diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..cd8eb86e --- /dev/null +++ b/.editorconfig @@ -0,0 +1,15 @@ +; This file is for unifying the coding style for different editors and IDEs. +; More information at http://editorconfig.org + +root = true + +[*] +charset = utf-8 +indent_size = 4 +indent_style = space +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..08747e83 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,13 @@ +# Path-based git attributes +# https://www.kernel.org/pub/software/scm/git/docs/gitattributes.html + +# Ignore all test and documentation with "export-ignore". +/.github export-ignore +/.gitattributes export-ignore +/.gitignore export-ignore +/.travis.yml export-ignore +/phpunit.xml.dist export-ignore +/.scrutinizer.yml export-ignore +/.styleci.yml export-ignore +/.editorconfig export-ignore +/tests export-ignore diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 00000000..d666650e --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,41 @@ +name: PHPUnit tests + +on: + - push + - pull_request + +jobs: + tests: + runs-on: ubuntu-latest + strategy: + fail-fast: true + matrix: + php: [7.3, 7.4, 8.0, 8.1] + laravel: [6.*, 7.*, 8.*] + dependency-version: [prefer-stable] + exclude: + - laravel: 6.* + php: 8.1 + - laravel: 7.* + php: 8.1 + + name: Tests on PHP ${{ matrix.php }} - L ${{ matrix.laravel }} + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + tools: composer:v2 + coverage: none + + - name: Install dependencies + run: | + composer require "laravel/framework:${{ matrix.laravel }}" --no-interaction --no-update + composer update --${{ matrix.dependency-version }} --no-interaction --no-progress + + - name: Execute tests + run: vendor/bin/phpunit --verbose diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..74715f52 --- /dev/null +++ b/.gitignore @@ -0,0 +1,31 @@ +# phpstorm project files +.idea + +# netbeans project files +nbproject + +# zend studio for eclipse project files +.buildpath +.project +.settings + +# windows thumbnail cache +Thumbs.db + +# Mac DS_Store Files +.DS_Store + +# composer +/vendor +composer.phar +composer.lock + +# phpunit +phpunit.phar +/phpunit.xml +.phpunit.result.cache + +# build +build +/.php-cs-fixer.cache +/coverage.clover diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php new file mode 100644 index 00000000..c31a163c --- /dev/null +++ b/.php-cs-fixer.dist.php @@ -0,0 +1,47 @@ +notPath('bootstrap/*') + ->notPath('storage/*') + ->notPath('resources/view/mail/*') + ->in([ + __DIR__.'/src', + __DIR__.'/tests', + ]) + ->name('*.php') + ->notName('*.blade.php') + ->ignoreDotFiles(true) + ->ignoreVCS(true); + +$config = new PhpCsFixer\Config(); +$config->setRiskyAllowed(true) + ->setRules([ + '@PSR2' => true, + 'array_syntax' => ['syntax' => 'short'], + 'ordered_imports' => ['sort_algorithm' => 'alpha'], + 'no_unused_imports' => true, + 'not_operator_with_successor_space' => true, + 'trailing_comma_in_multiline' => true, + 'phpdoc_scalar' => true, + 'unary_operator_spaces' => true, + 'binary_operator_spaces' => true, + 'blank_line_before_statement' => [ + 'statements' => ['break', 'continue', 'declare', 'return', 'throw', 'try'], + ], + 'phpdoc_single_line_var_spacing' => true, + 'phpdoc_var_without_name' => true, + 'class_attributes_separation' => [ + 'elements' => [ + 'method' => 'one', + ], + ], + 'method_argument_space' => [ + 'on_multiline' => 'ensure_fully_multiline', + 'keep_multiple_spaces_after_comma' => true, + ], + 'single_trait_insert_per_statement' => true, + ]) + ->setFinder($finder); + +return $config; diff --git a/.scrutinizer.yml b/.scrutinizer.yml new file mode 100644 index 00000000..6fad5be9 --- /dev/null +++ b/.scrutinizer.yml @@ -0,0 +1,21 @@ +filter: + excluded_paths: [tests/*] + +checks: + php: + remove_extra_empty_lines: true + remove_php_closing_tag: true + remove_trailing_whitespace: true + fix_use_statements: + remove_unused: true + preserve_multiple: false + preserve_blanklines: true + order_alphabetically: true + fix_php_opening_tag: true + fix_linefeed: true + fix_line_ending: true + fix_identation_4spaces: true + fix_doc_comments: true + +tools: + external_code_coverage: true diff --git a/.styleci.yml b/.styleci.yml new file mode 100644 index 00000000..6686423e --- /dev/null +++ b/.styleci.yml @@ -0,0 +1,5 @@ +preset: laravel +disabled: + - laravel_braces +enabled: + - psr12_braces diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100755 index 00000000..a72e497c --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,7 @@ +# Changelog + +All notable changes to `bulkgate` will be documented in this file + +## 1.0.0 - 2022-01-07 + +- initial release diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100755 index 00000000..4da74e3f --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,55 @@ +# Contributing + +Contributions are **welcome** and will be fully **credited**. + +Please read and understand the contribution guide before creating an issue or pull request. + +## Etiquette + +This project is open source, and as such, the maintainers give their free time to build and maintain the source code +held within. They make the code freely available in the hope that it will be of use to other developers. It would be +extremely unfair for them to suffer abuse or anger for their hard work. + +Please be considerate towards maintainers when raising issues or presenting pull requests. Let's show the +world that developers are civilized and selfless people. + +It's the duty of the maintainer to ensure that all submissions to the project are of sufficient +quality to benefit the project. Many developers have different skillsets, strengths, and weaknesses. Respect the maintainer's decision, and do not be upset or abusive if your submission is not used. + +## Viability + +When requesting or submitting new features, first consider whether it might be useful to others. Open +source projects are used by many developers, who may have entirely different needs to your own. Think about +whether or not your feature is likely to be used by other users of the project. + +## Procedure + +Before filing an issue: + +- Attempt to replicate the problem, to ensure that it wasn't a coincidental incident. +- Check to make sure your feature suggestion isn't already present within the project. +- Check the pull requests tab to ensure that the bug doesn't have a fix in progress. +- Check the pull requests tab to ensure that the feature isn't already in progress. + +Before submitting a pull request: + +- Check the codebase to ensure that your feature doesn't already exist. +- Check the pull requests to ensure that another person hasn't already submitted the feature or fix. + +## Requirements + +If the project maintainer has any additional requirements, you will find them listed here. + +- **[PSR-2 Coding Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)** - The easiest way to apply the conventions is to install [PHP Code Sniffer](http://pear.php.net/package/PHP_CodeSniffer). + +- **Add tests!** - Your patch won't be accepted if it doesn't have tests. + +- **Document any change in behaviour** - Make sure the `README.md` and any other relevant documentation are kept up-to-date. + +- **Consider our release cycle** - We try to follow [SemVer v2.0.0](http://semver.org/). Randomly breaking public APIs is not an option. + +- **One pull request per feature** - If you want to do more than one thing, send multiple pull requests. + +- **Send coherent history** - Make sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please [squash them](http://www.git-scm.com/book/en/v2/Git-Tools-Rewriting-History#Changing-Multiple-Commit-Messages) before submitting. + +**Happy coding**! diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 00000000..d4ce3f49 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +# The MIT License (MIT) + +Copyright (c) Martin Vlcek + +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. diff --git a/README.md b/README.md index fa8362b7..8fd20e8c 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,135 @@ -# New Notification Channels -### Suggesting a new channel -Have a suggestion or working on a new channel? Please create a new issue for that service. +# BulkGate SMS notification channel for Laravel -### I'm working on a new channel -Please create an issue for it if it does not already exist, then PR you code for review. +[![Latest Version on Packagist](https://img.shields.io/packagist/v/laravel-notification-channels/bulkgate.svg?style=flat-square)](https://packagist.org/packages/laravel-notification-channels/bulkgate) +[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE.md) +[![Build Status](https://img.shields.io/travis/laravel-notification-channels/bulkgate/master.svg?style=flat-square)](https://travis-ci.org/laravel-notification-channels/bulkgate) +[![StyleCI](https://github.styleci.io/repos/431969862/shield?branch=bulkgate)](https://github.styleci.io/repos/431969862?branch=bulkgate) +[![Quality Score](https://img.shields.io/scrutinizer/g/laravel-notification-channels/bulkgate.svg?style=flat-square)](https://scrutinizer-ci.com/g/laravel-notification-channels/bulkgate) +[![Code Coverage](https://img.shields.io/scrutinizer/coverage/g/laravel-notification-channels/bulkgate/master.svg?style=flat-square)](https://scrutinizer-ci.com/g/laravel-notification-channels/bulkgate/?branch=master) +[![Total Downloads](https://img.shields.io/packagist/dt/laravel-notification-channels/bulkgate.svg?style=flat-square)](https://packagist.org/packages/laravel-notification-channels/bulkgate) -## Workflow for new channels +This package makes it easy to send notifications using [BulkGate](https://bulkgate.com) with Laravel 6.x, 7.x and 8.x. -1) Head over to the [skeleton repo](https://github.com/laravel-notification-channels/skeleton) download a ZIP copy. This is important, to ensure you start from a fresh commit history. -2) Use find/replace to replace all of the placeholders with the correct values (package name, author name, email, etc). -3) Implement to logic for the channel & add tests. -4) Fork this repo, add it as a remote and push your new channel to a branch. -5) Submit a new PR against this repo for review. +## Contents -Take a look at our [FAQ](http://laravel-notification-channels.com/) to see our small list of rules, to provide top-notch notification channels. +- [Installation](#installation) + - [Setting up the BulkGate service](#setting-up-the-BulkGate-service) + - [Advanced configuration](#advanced-configuration) +- [Usage](#usage) + - [Routing Bulkgate notifications](#routing-bulkgate-notifications) + - [Available Message methods](#available-message-methods) + - [Notification](#notification) +- [Changelog](#changelog) +- [Testing](#testing) +- [Security](#security) +- [Contributing](#contributing) +- [Credits](#credits) +- [License](#license) + + +## Installation +You can install the package via composer: + +``` bash +composer require laravel-notification-channels/bulkgate +``` + +Or you can manually update composer.json and run `composer update`: +```json +{ + "require": { + "laravel-notification-channels/bulkgate": "^1.0" + } +} +``` + +### Setting up the BulkGate service + +Add your BulkGate App ID and App Token to your .env: + +``` +BULKGATE_APP_ID=12345 # always required +BULKGATE_APP_TOKEN=ABCD # always required +BULKGATE_SEND_UNICODE=true # optional +BULKGATE_SENDER_ID=12345 # optional +``` + +([How to get API Access data](https://help.bulkgate.com/docs/en/api-administration.html#how-do-i-get-api-access-data)) + +### Advanced configuration + +Publish config file `bulkgate-notification-channel.php`: +``` bash +php artisan vendor:publish --provider="NotificationChannels\BulkGate\BulkGateServiceProvider" +``` + +## Usage + +### Routing Bulkgate notifications +In order to send notifications to BulkGate, you need to specify recipient phone number. +The channel will automatically add the recipient phone number to the notification message from the `phone_number` +attribute of the notifiable model. If you want to use a different attribute, you can specify it in the `routeNotificationForBulkGate` method. + +```php +public function routeNotificationForBulkGate() +{ + return $this->another_phone_number; +} +``` +### Notification + +Now you can use the channel in your `via()` method inside the notification: +``` php +use Illuminate\Notifications\Notification; +use NotificationChannels\BulkGate\BulkGateChannel; +use NotificationChannels\BulkGate\BulkGateMessage; + +class OrderShipped extends Notification +{ + + public function via($notifiable): array + { + return [BulkGateChannel::class]; + } + + public function toBulkGate($notifiable): BulkGateMessage + { + return BulkGateMessage::create('Your order has been shipped!'); + // or + return (new BulkGateMessage)->text('Your order has been shipped!'); + } +} +``` + +### Available Message methods + + * `text($text)` - _(string)_ Text message + * `to($phoneNumber)` - _(string)_ Recipient phone number. This will override the recipient phone number from the notifiable model. + +## Changelog + +Please see [CHANGELOG](CHANGELOG.md) for more information what has changed recently. + +## Testing + +``` bash +$ composer test +``` + +## Security + +If you discover any security related issues, please email martin@dontfreakout.eu instead of using the issue tracker. + +## Contributing + +Please see [CONTRIBUTING](CONTRIBUTING.md) for details. + +## Credits + +- [Martin Vlcek](https://github.com/dontfreakout) +- [All Contributors](../../contributors) + +## License + +The MIT License (MIT). Please see [License File](LICENSE.md) for more information. diff --git a/composer.json b/composer.json new file mode 100644 index 00000000..76aedaf1 --- /dev/null +++ b/composer.json @@ -0,0 +1,51 @@ +{ + "name": "laravel-notification-channels/bulkgate", + "description": "Provides BulkGate SMS notification channel for Laravel", + "homepage": "https://github.com/laravel-notification-channels/bulkgate", + "license": "MIT", + "authors": [ + { + "name": "Martin Vlcek", + "email": "martin@dontfreakout.eu", + "homepage": "https://github.com/dontfreakout", + "role": "Developer" + } + ], + "require": { + "php": ">=7.3", + "bulkgate/sms": "^2.1", + "illuminate/notifications": "~6.0 || ~7.0 || ~8.0", + "illuminate/support": "~6.0 || ~7.0 || ~8.0", + "illuminate/events": "~6.0 || ~7.0 || ~8.0", + "illuminate/queue": "^6.0 || ^7.0 || ^8.0" + }, + "require-dev": { + "mockery/mockery": "^1.0", + "phpunit/phpunit": "^9.0", + "orchestra/testbench": ">= v4.18" + }, + "autoload": { + "psr-4": { + "NotificationChannels\\BulkGate\\": "src" + } + }, + "autoload-dev": { + "psr-4": { + "NotificationChannels\\BulkGate\\Test\\": "tests" + } + }, + "scripts": { + "test": "vendor/bin/phpunit", + "test:coverage": "vendor/bin/phpunit --coverage-text --coverage-clover=coverage.clover" + }, + "config": { + "sort-packages": true + }, + "extra": { + "laravel": { + "providers": [ + "NotificationChannels\\BulkGate\\BulkGateServiceProvider" + ] + } + } +} diff --git a/config/bulkgate-notification-channel.php b/config/bulkgate-notification-channel.php new file mode 100644 index 00000000..be86466b --- /dev/null +++ b/config/bulkgate-notification-channel.php @@ -0,0 +1,32 @@ + env('BULKGATE_APP_ID'), + 'app_token' => env('BULKGATE_APP_TOKEN'), + 'send_unicode' => env('BULKGATE_SEND_UNICODE', false), + + // If you enter phone numbers in a national format, the system does not know where to send the messages. + // Hence, unless the international prefix is explicitly filled in, you can define the default country + // for the sender type to which the messages will be routed. + // Via BulkGate\Sms\Country + 'default_country' => BulkGate\Sms\Country::UNITED_KINGDOM, + // Or ISO 3166-1 alpha-2 + // 'default_country' => 'gb', + + // Choose the sender type to which the messages will be routed. + // Available options: + // ----------------- + // BulkGate\Sms\SenderSettings\Gate::GATE_SYSTEM_NUMBER + // BulkGate\Sms\SenderSettings\Gate::GATE_SHORT_CODE + // BulkGate\Sms\SenderSettings\Gate::GATE_TEXT_SENDER + // BulkGate\Sms\SenderSettings\Gate::GATE_OWN_NUMBER + // BulkGate\Sms\SenderSettings\Gate::GATE_MOBILE_CONNECT + // See https://help.bulkgate.com/docs/en/php-sdk-sender-settings.html for more details + 'sender_type' => BulkGate\Sms\SenderSettings\Gate::GATE_SYSTEM_NUMBER, + + // Sender ID is required if sender_type is GATE_TEXT_SENDER or GATE_OWN_NUMBER + // The maximal length of $value in case of the text sender ID is 11 characters of the English alphabet. + // If you choose own number or mobile sender type it is necessary to verify on BulkGate Portal the number first. + 'sender_id' => env('BULKGATE_SENDER_ID', ''), +]; diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 00000000..84c8f2a8 --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,21 @@ + + + + + src + + + + + + + + + + tests + + + + + + diff --git a/src/BulkGateChannel.php b/src/BulkGateChannel.php new file mode 100644 index 00000000..4e043d94 --- /dev/null +++ b/src/BulkGateChannel.php @@ -0,0 +1,107 @@ +sender = $sender; + $this->dispatcher = $dispatcher; + } + + /** + * Send the given notification. + * + * @param mixed $notifiable + * + * @throws \NotificationChannels\BulkGate\Exceptions\CouldNotSendNotification + * @throws \Exception + */ + public function send($notifiable, Notification $notification): \BulkGate\Message\Response + { + if (! method_exists($notification, 'toBulkGate')) { + $exception = CouldNotSendNotification::undefinedMethod($notification); + + $this->notifyFail($notifiable, $notification, $exception); + + throw $exception; + } + + /** @var \NotificationChannels\BulkGate\BulkGateMessage $message */ + if (! ($message = $notification->toBulkGate($notification)) instanceof BulkGateMessage) { + $exception = CouldNotSendNotification::invalidMessage($message); + + $this->notifyFail($notifiable, $notification, $exception); + + throw $exception; + } + + if (! $message->hasPhoneNumber()) { + $message->to($this->getTo($notifiable)); + } + + try { + $response = $this->sender->send($message->getMessage()); + + if (! $response->isSuccess()) { + throw CouldNotSendNotification::serviceRespondedWithAnError($response); + } + + return $response; + } catch (Exception $exception) { + $this->notifyFail($notifiable, $notification, $exception); + + throw $exception; + } + } + + /** + * @param mixed $notifiable + * @param null|mixed $notification + * + * @throws \NotificationChannels\BulkGate\Exceptions\CouldNotSendNotification + */ + protected function getTo($notifiable, $notification = null) + { + if ($notifiable->routeNotificationFor(self::class, $notification)) { + return $notifiable->routeNotificationFor(self::class, $notification); + } + if ($notifiable->routeNotificationFor('bulkgate', $notification)) { + return $notifiable->routeNotificationFor('bulkgate', $notification); + } + if (isset($notifiable->phone_number)) { + return $notifiable->phone_number; + } + + throw CouldNotSendNotification::invalidReceiver(); + } + + protected function notifyFail($notifiable, $notification, $exception) + { + $event = new NotificationFailed( + $notifiable, + $notification, + 'bulkgate', + ['message' => $exception->getMessage(), 'exception' => $exception] + ); + + $this->dispatcher->dispatch($event); + } +} diff --git a/src/BulkGateMessage.php b/src/BulkGateMessage.php new file mode 100644 index 00000000..2577e0d3 --- /dev/null +++ b/src/BulkGateMessage.php @@ -0,0 +1,59 @@ +phone_number = $phone_number; + $this->country_code = $country_code; + + return $this; + } + + public function text(string $text): self + { + $this->text = $text; + + return $this; + } + + public function getMessage(): \BulkGate\Sms\Message + { + $message = new \BulkGate\Sms\Message($this->phone_number, $this->text); + + if ($this->country_code) { + $message->phoneNumber($this->phone_number, $this->country_code); + } + + return $message; + } + + public function hasPhoneNumber(): bool + { + return null !== $this->phone_number; + } + + public static function create(string $text, string $phone_number = null, string $country_code = null): self + { + $message = new self(); + + if (! is_null($phone_number)) { + $message->to($phone_number, $country_code); + } + + $message->text($text); + + return $message; + } +} diff --git a/src/BulkGateNotificationConfig.php b/src/BulkGateNotificationConfig.php new file mode 100644 index 00000000..41a6cf90 --- /dev/null +++ b/src/BulkGateNotificationConfig.php @@ -0,0 +1,78 @@ +settings = $settings; + $this->validateConfiguration(); + } + + public function getSendUnicode(): bool + { + return $this->settings['send_unicode']; + } + + public function getAppId(): int + { + return (int) $this->settings['app_id']; + } + + public function getAppToken(): string + { + return (string) $this->settings['app_token']; + } + + public function getDefaultCountry(): string + { + return $this->settings['default_country']; + } + + public function getSenderType(): string + { + return $this->settings['sender_type']; + } + + public function getSenderId(): string + { + return $this->settings['sender_id']; + } + + /** + * @throws \NotificationChannels\BulkGate\Exceptions\InvalidConfigException + */ + protected function validateConfiguration() + { + if (! isset($this->settings['app_id']) || ! is_int((int) $this->settings['app_id'])) { + throw InvalidConfigException::invalidConfiguration('BulkGate app id is missing or invalid.'); + } + + if (! isset($this->settings['app_token']) || ! is_string($this->settings['app_token'])) { + throw InvalidConfigException::invalidConfiguration('BulkGate app token is missing or invalid.'); + } + + if (in_array($this->settings['sender_type'], [Gate::GATE_OWN_NUMBER, Gate::GATE_TEXT_SENDER], true) + && empty($this->getSenderId()) + ) { + throw InvalidConfigException::invalidConfiguration('Sender ID is required for this type of sender. Set sender_id in configuration file or use another sender type.'); + } + + if (Gate::GATE_TEXT_SENDER === $this->settings['sender_type'] + && strlen($this->settings['sender_id']) > 11) { + throw InvalidConfigException::invalidConfiguration('Sender ID must be 11 characters or less for this sender type.'); + } + } +} diff --git a/src/BulkGateServiceProvider.php b/src/BulkGateServiceProvider.php new file mode 100644 index 00000000..e1874098 --- /dev/null +++ b/src/BulkGateServiceProvider.php @@ -0,0 +1,68 @@ +mergeConfigFrom(__DIR__.'/../config/bulkgate-notification-channel.php', 'bulkgate-notification-channel'); + + $this->publishes([ + __DIR__.'/../config/bulkgate-notification-channel.php' => config_path('bulkgate-notification-channel.php'), + ]); + + $this->app->bind(BulkGateNotificationConfig::class, function (Application $app) { + return new BulkGateNotificationConfig($app['config']['bulkgate-notification-channel']); + }); + + $this->app->singleton(Sender::class, function (Application $app) { + /** @var \NotificationChannels\BulkGate\BulkGateNotificationConfig $config */ + $config = $app->make(BulkGateNotificationConfig::class); + + $connection = new Connection($config->getAppId(), $config->getAppToken()); + + switch ($config->getSenderType()) { + case Gate::GATE_OWN_NUMBER: + case Gate::GATE_TEXT_SENDER: + $sender_settings = new StaticSenderSettings($config->getSenderType(), $config->getSenderId()); + + break; + default: + $sender_settings = new StaticSenderSettings($config->getSenderType()); + } + + $sender = new Sender($connection); + + $sender->setSenderSettings($sender_settings); + + $sender->unicode($config->getSendUnicode()); + + $sender->setDefaultCountry($config->getDefaultCountry()); + + return $sender; + }); + + $this->app->singleton(BulkGateChannel::class, function (Application $app) { + return new BulkGateChannel($app->make(Sender::class), $app->make(Dispatcher::class)); + }); + } +} diff --git a/src/Exceptions/CouldNotSendNotification.php b/src/Exceptions/CouldNotSendNotification.php new file mode 100644 index 00000000..4ebcd24e --- /dev/null +++ b/src/Exceptions/CouldNotSendNotification.php @@ -0,0 +1,48 @@ +error ?? 'undefined error').'`' + ); + } + + public static function undefinedMethod(\Illuminate\Notifications\Notification $notification): self + { + return new static( + 'Notification of class: '.get_class($notification) + .' must define a `toBulkGateSms()` method in order to send via the BulkGate SMS channel.' + ); + } + + public static function invalidMessage($message): self + { + return new static( + 'Message must be an instance of '.BulkGateMessage::class.', '.gettype($message).' given.' + ); + } + + public static function unexpectedException(\Exception $exception): self + { + return new static( + 'Failed to send SMS message, unexpected exception encountered: `'.$exception->getMessage().'`', + 0, + $exception + ); + } + + public static function invalidReceiver(): self + { + return new static( + 'The notifiable did not have a receiving phone number. Add a routeNotificationForBulkGateSms method + to your notifiable model or a phone_number attribute to handle this.' + ); + } +} diff --git a/src/Exceptions/InvalidConfigException.php b/src/Exceptions/InvalidConfigException.php new file mode 100644 index 00000000..8746a947 --- /dev/null +++ b/src/Exceptions/InvalidConfigException.php @@ -0,0 +1,16 @@ +text('Test message'); + } +} diff --git a/tests/Fixtures/TestNotificationWithCustomRecipient.php b/tests/Fixtures/TestNotificationWithCustomRecipient.php new file mode 100644 index 00000000..9bd3dc96 --- /dev/null +++ b/tests/Fixtures/TestNotificationWithCustomRecipient.php @@ -0,0 +1,22 @@ +expectException(InvalidConfigException::class); + + $this->app->get(BulkGateChannel::class); + } + + public function testApplicationCreatesChannelWithConfig() + { + $this->app['config']->set('bulkgate-notification-channel.app_id', '1234'); + $this->app['config']->set('bulkgate-notification-channel.app_token', 'password'); + + $this->assertInstanceOf(BulkGateChannel::class, $this->app->get(BulkGateChannel::class)); + } +} diff --git a/tests/Unit/BulkGateChannelTest.php b/tests/Unit/BulkGateChannelTest.php new file mode 100644 index 00000000..64752136 --- /dev/null +++ b/tests/Unit/BulkGateChannelTest.php @@ -0,0 +1,167 @@ +dispatcher = Mockery::mock(Dispatcher::class); + $this->sender = Mockery::mock(Sender::class); + $this->response = Mockery::mock(Response::class); + } + + public function testSuccessSend() + { + $this->sender->shouldReceive('send') + ->withArgs(function ($message) { + return '71234567890' === $message->getPhoneNumber()->getPhoneNumber(); + }) + ->andReturn($this->response); + + $this->response->shouldReceive('isSuccess') + ->andReturn(true); + + $this->dispatcher->shouldReceive('dispatch') + ->with(BulkGateSmsSent::class); + + $channel = new BulkGateChannel($this->sender, $this->dispatcher); + $channel->send(new NotifiableWithAttribute(), new TestNotificationStaticCreate()); + } + + public function testNonStaticMessageCreateSend() + { + $this->sender->shouldReceive('send') + ->withArgs(function ($message) { + return '71234567890' === $message->getPhoneNumber()->getPhoneNumber(); + }) + ->andReturn($this->response); + + $this->response->shouldReceive('isSuccess') + ->andReturn(true); + + $this->dispatcher->shouldReceive('dispatch') + ->with(BulkGateSmsSent::class); + + $channel = new BulkGateChannel($this->sender, $this->dispatcher); + $channel->send(new NotifiableWithAttribute(), new TestNotification()); + } + + public function testSendToNotifiableWithRoute() + { + $this->sender->shouldReceive('send') + ->withArgs(function ($message) { + return '71234567890' === $message->getPhoneNumber()->getPhoneNumber(); + }) + ->andReturn($this->response); + + $this->response->shouldReceive('isSuccess') + ->andReturn(true); + + $this->dispatcher->shouldReceive('dispatch') + ->with(BulkGateSmsSent::class); + + $channel = new BulkGateChannel($this->sender, $this->dispatcher); + $channel->send(new NotifiableWithRoute(), new TestNotificationStaticCreate()); + } + + public function testSendToCustomRecipient() + { + $this->sender->shouldReceive('send') + ->withArgs(function ($message) { + return TestNotificationWithCustomRecipient::RECIPIENT === $message->getPhoneNumber()->getPhoneNumber(); + }) + ->andReturn($this->response); + + $this->response->shouldReceive('isSuccess') + ->andReturn(true); + + $this->dispatcher->shouldReceive('dispatch') + ->with(BulkGateSmsSent::class); + + $channel = new BulkGateChannel($this->sender, $this->dispatcher); + $channel->send(new NotifiableWithAttribute(), new TestNotificationWithCustomRecipient()); + } + + public function testRiseExceptionWhenInvalidMessage() + { + $this->dispatcher->shouldReceive('dispatch') + ->with(NotificationFailed::class); + + $this->expectException(CouldNotSendNotification::class); + $this->expectExceptionMessageMatches('/^Message must be an instance of/'); + + $channel = new BulkGateChannel($this->sender, $this->dispatcher); + $channel->send(new NotifiableWithAttribute(), new InvalidMessageTestNotification()); + } + + public function testRiseExceptionWhenNotificationHasNoToBulkGateRoute() + { + $this->dispatcher->shouldReceive('dispatch') + ->with(NotificationFailed::class); + + $this->expectException(CouldNotSendNotification::class); + $this->expectExceptionMessageMatches('/method in order to send via the BulkGate SMS channel\.$/'); + + $channel = new BulkGateChannel($this->sender, $this->dispatcher); + $channel->send(new NotifiableWithAttribute(), new MissingRouteTestNotification()); + } + + public function testRiseExceptionWhenInvalidResponse() + { + $this->dispatcher->shouldReceive('dispatch') + ->with(NotificationFailed::class); + + $this->sender->shouldReceive('send') + ->andReturn($this->response); + + $this->response->shouldReceive('isSuccess') + ->andReturn(false); + + $this->expectException(CouldNotSendNotification::class); + $this->expectExceptionMessageMatches('/^BulkGate responded with an error:/'); + + $channel = new BulkGateChannel($this->sender, $this->dispatcher); + $channel->send(new NotifiableWithAttribute(), new TestNotificationStaticCreate()); + } +} + +class NotifiableWithAttribute +{ + use Notifiable; + public $phone_number = '+71234567890'; +} + +class NotifiableWithRoute +{ + use Notifiable; + + public function routeNotificationForBulkGate() + { + return '+71234567890'; + } +} diff --git a/tests/Unit/BulkGateMessageTest.php b/tests/Unit/BulkGateMessageTest.php new file mode 100644 index 00000000..374804bf --- /dev/null +++ b/tests/Unit/BulkGateMessageTest.php @@ -0,0 +1,71 @@ +to('+420123456789'); + + $this->assertEquals('420123456789', $message->getMessage()->getPhoneNumber()->getPhoneNumber()); + } + + public function testToWithCountryCode() + { + $message = new BulkGateMessage(); + + $message->to('123456789', 'uk'); + + $this->assertEquals('123456789', $message->getMessage()->getPhoneNumber()->getPhoneNumber()); + $this->assertEquals('uk', $message->getMessage()->getPhoneNumber()->getIso()); + } + + public function testTextOnly() + { + $message = new BulkGateMessage(); + + $message->to('+420123456789') + ->text('Hello World'); + + $this->assertEquals('Hello World', $message->getMessage()->getText()); + } + + public function testHasPhoneNumber() + { + $message = new BulkGateMessage(); + + $message->to('+420123456789'); + + $this->assertTrue($message->hasPhoneNumber()); + } + + public function testStaticCreateTextOnly() + { + $message = BulkGateMessage::create('Hello World'); + + $message->to('+420123456789'); + + $this->assertEquals('Hello World', $message->getMessage()->getText()); + } + + public function testStaticCreateWithAllParameters() + { + $message = BulkGateMessage::create('HelloWorld', '+420123456789', 'uk'); + + $this->assertEquals('HelloWorld', $message->getMessage()->getText()); + $this->assertEquals('420123456789', $message->getMessage()->getPhoneNumber()->getPhoneNumber()); + $this->assertEquals('uk', $message->getMessage()->getPhoneNumber()->getIso()); + } +} diff --git a/tests/Unit/BulkGateNotificationConfigTest.php b/tests/Unit/BulkGateNotificationConfigTest.php new file mode 100644 index 00000000..9f7a529e --- /dev/null +++ b/tests/Unit/BulkGateNotificationConfigTest.php @@ -0,0 +1,127 @@ +validConfig = [ + 'app_id' => '123456789', + 'app_token' => '123456789', + 'send_unicode' => true, + 'default_country' => 'gb', + 'sender_type' => 'gSystem', + 'sender_id' => '123456789', + ]; + } + + public function testGetSenderType() + { + $config = new \NotificationChannels\BulkGate\BulkGateNotificationConfig($this->validConfig); + $this->assertEquals('gSystem', $config->getSenderType()); + } + + public function testGetSenderId() + { + $config = new \NotificationChannels\BulkGate\BulkGateNotificationConfig($this->validConfig); + $this->assertEquals('123456789', $config->getSenderId()); + } + + public function testGetAppToken() + { + $config = new \NotificationChannels\BulkGate\BulkGateNotificationConfig($this->validConfig); + $this->assertEquals('123456789', $config->getAppToken()); + } + + public function testGetDefaultCountry() + { + $config = new \NotificationChannels\BulkGate\BulkGateNotificationConfig($this->validConfig); + $this->assertEquals('gb', $config->getDefaultCountry()); + } + + public function testGetAppId() + { + $config = new \NotificationChannels\BulkGate\BulkGateNotificationConfig($this->validConfig); + $this->assertEquals('123456789', $config->getAppId()); + $this->assertIsInt($config->getAppId()); + } + + public function testGetSendUnicode() + { + $config = new \NotificationChannels\BulkGate\BulkGateNotificationConfig($this->validConfig); + $this->assertTrue($config->getSendUnicode()); + } + + public function testThrowExceptionWhenSenderIdTooLong() + { + $this->expectException(InvalidConfigException::class); + $this->expectExceptionMessage('Sender ID must be 11 characters or less for this sender type.'); + + $invalid_config = new \NotificationChannels\BulkGate\BulkGateNotificationConfig([ + 'app_id' => '123456789', + 'app_token' => '123456789', + 'send_unicode' => true, + 'default_country' => 'gb', + 'sender_type' => \BulkGate\Sms\SenderSettings\Gate::GATE_TEXT_SENDER, + 'sender_id' => '0123456789123456789', + ]); + } + + public function testThrowExceptionWhenEmptySenderId() + { + $this->expectException(InvalidConfigException::class); + $this->expectExceptionMessage('Sender ID is required for this type of sender. Set sender_id in configuration file or use another sender type.'); + + $invalid_config = new \NotificationChannels\BulkGate\BulkGateNotificationConfig([ + 'app_id' => '123456789', + 'app_token' => '123456789', + 'send_unicode' => true, + 'default_country' => 'gb', + 'sender_type' => \BulkGate\Sms\SenderSettings\Gate::GATE_TEXT_SENDER, + 'sender_id' => '', + ]); + + $this->expectException(InvalidConfigException::class); + $this->expectExceptionMessage('Sender ID is required for this type of sender. Set sender_id in configuration file or use another sender type.'); + + $invalid_config = new \NotificationChannels\BulkGate\BulkGateNotificationConfig([ + 'app_id' => '123456789', + 'app_token' => '123456789', + 'send_unicode' => true, + 'default_country' => 'gb', + 'sender_type' => \BulkGate\Sms\SenderSettings\Gate::GATE_OWN_NUMBER, + 'sender_id' => '', + ]); + } + + public function testLongSenderIdWhenSenderIsOwnNumber() + { + $config = new \NotificationChannels\BulkGate\BulkGateNotificationConfig([ + 'app_id' => '123456789', + 'app_token' => '123456789', + 'send_unicode' => true, + 'default_country' => 'gb', + 'sender_type' => \BulkGate\Sms\SenderSettings\Gate::GATE_OWN_NUMBER, + 'sender_id' => '+44123456789', + ]); + + $this->assertEquals('+44123456789', $config->getSenderId()); + } +}