diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..b263871 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,10 @@ +# Path-based git attributes +# https://www.kernel.org/pub/software/scm/git/docs/gitattributes.html + +# Ignore all test and documentation with "export-ignore". +/.gitattributes export-ignore +/.gitignore export-ignore +/.travis.yml export-ignore +/phpunit.xml.dist export-ignore +/.scrutinizer.yml export-ignore +/tests export-ignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3e09690 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +/vendor +/build +composer.phar +composer.lock +/.idea \ No newline at end of file diff --git a/.scrutinizer.yml b/.scrutinizer.yml new file mode 100644 index 0000000..6fad5be --- /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 0000000..c3bb259 --- /dev/null +++ b/.styleci.yml @@ -0,0 +1 @@ +preset: laravel \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..abadb30 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,23 @@ +language: php + +php: + - 5.6 + - 7.0 + - 7.1 + +env: + matrix: + - COMPOSER_FLAGS="--prefer-lowest" + - COMPOSER_FLAGS="" + +before_script: + - travis_retry composer self-update + - travis_retry composer update ${COMPOSER_FLAGS} --no-interaction --prefer-source + +script: + - phpunit --coverage-text --coverage-clover=coverage.clover + +after_script: + - wget https://scrutinizer-ci.com/ocular.phar + - php ocular.phar code-coverage:upload --format=php-clover coverage.clover + diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..7100bc9 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,11 @@ +# Changelog + +All notable changes to `gosms` will be documented in this file + +## 1.0.0 - 2017-10-28 +- initial release +- better config support + +## 0.0.1 - 2017-10-27 + +- experimental release diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..4da74e3 --- /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 0000000..4ccbb5d --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +# The MIT License (MIT) + +Copyright © Shiro Amada + +> 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 new file mode 100644 index 0000000..aae7b03 --- /dev/null +++ b/README.md @@ -0,0 +1,124 @@ +# UltraSms notifications channel for Laravel 5.3+ + + +[![Latest Version on Packagist](https://img.shields.io/packagist/v/shiroamada/ultrasms.svg?style=flat-square)](https://packagist.org/packages/shiroamada/ultrasms-laravel-notification) +[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE.md) +[![Build Status](https://img.shields.io/travis/shiroamada/ultrasms-laravel-notification/master.svg?style=flat-square)](https://travis-ci.org/shiroamada/gosms) +[![StyleCI](https://styleci.io/repos/108503043/shield)](https://styleci.io/repos/108503043) +[![Quality Score](https://img.shields.io/scrutinizer/g/laravel-notification-channels/smsc-ru.svg?style=flat-square)](https://scrutinizer-ci.com/g/laravel-notification-channels/ultrasms-laravel-notification) +[![Code Coverage](https://img.shields.io/scrutinizer/coverage/g/laravel-notification-channels/smsc-ru/master.svg?style=flat-square)](https://scrutinizer-ci.com/g/shiroamada/ultrasms-laravel-notification/?branch=main) +[![Total Downloads](https://img.shields.io/packagist/dt/shiroamada/ultrasms-laravel-notification.svg?style=flat-square)](https://packagist.org/packages/shiroamada/ultrasms-laravel-notification) + +This package makes it easy to send notifications using [https://ultramsg.com/](https://ultramsg.com/) with Laravel 5.3+. + +Code Reference from laravel-notification-channels/smsc-ru + +## Contents + +- [Installation](#installation) + - [Setting up the UltraSMS service](#setting-up-the-ultrasms-service) +- [Usage](#usage) + - [Available Message methods](#available-message-methods) +- [Changelog](#changelog) +- [Testing](#testing) +- [Security](#security) +- [Contributing](#contributing) +- [Credits](#credits) +- [License](#license) + + +## Installation + +You can install the package via composer: + +```bash +composer require shiroamada/ultrasms-laravel-notifiation +``` + +Then you must install the service provider: +```php +// config/app.php +'providers' => [ + ... + NotificationChannels\UltraSms\UltraSmsServiceProvider::class, +], +``` + +### Setting up the UltraSMS service + +Add your ultrasms instanceId and token to your `config/services.php`: + +```php +// config/services.php +... +'ultrasms' => [ + 'instanceId' => env('ULTRASMS_INSTANCEID'), + 'token' => env('ULTRASMS_TOKEN'), +], +... +``` + +## Usage + +You can use the channel in your `via()` method inside the notification: + +```php +use Illuminate\Notifications\Notification; +use NotificationChannels\UltraSms\UltraSmsMessage; +use NotificationChannels\UltraSms\UltraSmsChannel; + +class AccountApproved extends Notification +{ + public function via($notifiable) + { + return [UltraSmsChannel::class]; + } + + public function toUltraSms($notifiable) + { + return UltraSmsMessage::create("Task #{$notifiable->id} is complete!"); + } +} +``` + +In your notifiable model, make sure to include a routeNotificationForUltraSms() method, which return the phone number. + +```php +public function routeNotificationForUltraSms() +{ + return $this->mobile; //depend what is your db field +} +``` + +### Available methods + +`content()`: Set a content of the notification message. + +`sendAt()`: Set a time for scheduling the notification message. + +## 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 use the issue tracker. + +## Contributing + +Please see [CONTRIBUTING](CONTRIBUTING.md) for details. + +## Credits + +- [ShiroAmada](https://github.com/shiroamada) +- [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 0000000..f38908a --- /dev/null +++ b/composer.json @@ -0,0 +1,51 @@ +{ + "name": "shiroamada/ultrasms-laravel-notification", + "description": "UltraSMS Notifications channel for Laravel 5.3 and Above", + "keywords": ["laravel", "notifications", "ultrasms"], + "homepage": "https://github.com/shiroamada/ultrasms-laravel-notification", + "license": "MIT", + "authors": [ + { + "name": "Shiro Amada", + "role": "Developer", + "email": "shiro_amada08th@yahoo.com", + "homepage": "https://github.com/shiroamada" + } + ], + "require": { + "php": "^7.1", + "ext-json": "*", + "ext-mbstring": "*", + "guzzlehttp/guzzle": "^6.2", + "illuminate/queue": "5.1.*|5.2.*|5.3.*|5.4.*|5.5.*|5.6.*|5.7.*|5.8.*|^6.0", + "illuminate/notifications": "5.1.*|5.2.*|5.3.*|5.4.*|5.5.*|5.6.*|5.7.*|5.8.*|^6.0", + "illuminate/support": "5.1.*|5.2.*|5.3.*|5.4.*|5.5.*|5.6.*|5.7.*|5.8.*|^6.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.2", + "mockery/mockery": "^0.9.5" + }, + "autoload": { + "psr-4": { + "NotificationChannels\\UltraSms\\": "src" + } + }, + "autoload-dev": { + "psr-4": { + "NotificationChannels\\UltraSms\\Test\\": "tests" + } + }, + "config": { + "sort-packages": true + }, + "scripts": { + "test": "vendor/bin/phpunit" + }, + "extra": { + "laravel": { + "providers": [ + "NotificationChannels\\UltraSms\\UltraSmsServiceProvider" + ] + } + } +} diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..1f7c5c7 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,29 @@ + + + + + tests + + + + + src/ + + + + + + + + + + diff --git a/src/Exceptions/CouldNotSendNotification.php b/src/Exceptions/CouldNotSendNotification.php new file mode 100644 index 0000000..7ebfdf4 --- /dev/null +++ b/src/Exceptions/CouldNotSendNotification.php @@ -0,0 +1,57 @@ +getCode()}: {$exception->getMessage()}'" + ); + } + + /** + * Thrown when we're unable to communicate with smsc.ru. + * + * @param Exception $exception + * + * @return static + */ + public static function couldNotCommunicateWithUltraSms(Exception $exception) + { + return new static("The communication with ultrasms failed. Reason: {$exception->getMessage()}"); + } +} diff --git a/src/UltraSmsApi.php b/src/UltraSmsApi.php new file mode 100644 index 0000000..9e1ea74 --- /dev/null +++ b/src/UltraSmsApi.php @@ -0,0 +1,70 @@ +instanceId = $config['instanceId']; + $this->token = $config['token']; + + $this->httpClient = new HttpClient([ + 'base_uri' => $this->apiUrl.$this->instanceId.$this->action, + 'timeout' => 2.0, + ]); + } + + /** + * @param array $params + * + * @return array + * + * @throws CouldNotSendNotification + */ + public function send($params) + { + try { + $sendsms_url = "?token={$this->token}&priority={$this->priority}&to={$params['to']}&body={$params['body']}"; + + $response = $this->httpClient->request('GET', $this->apiUrl.$sendsms_url); + + $stream = $response->getBody(); + + $content = $stream->getContents(); + + $response = json_decode((string) $response->getBody(), true); + + return $response; + } catch (DomainException $exception) { + throw CouldNotSendNotification::exceptionUltraSmsRespondedWithAnError($exception); + } catch (\Exception $exception) { + throw CouldNotSendNotification::couldNotCommunicateWithUltraSms($exception); + } + } +} diff --git a/src/UltraSmsChannel.php b/src/UltraSmsChannel.php new file mode 100644 index 0000000..8743dde --- /dev/null +++ b/src/UltraSmsChannel.php @@ -0,0 +1,70 @@ +ultrasms = $ultrasms; + } + + /** + * Send the given notification. + * + * @param mixed $notifiable + * @param \Illuminate\Notifications\Notification $notification + * + * @throws \NotificationChannels\UltraSms\Exceptions\CouldNotSendNotification + */ + public function send($notifiable, Notification $notification) + { + $to = $notifiable->routeNotificationFor('ultrasms'); + + if (empty($to)) { + throw CouldNotSendNotification::missingRecipient(); + } + + $message = $notification->toUltraSms($notifiable); + + if (is_string($message)) { + $message = new UltraSmsMessage($message); + } + + $this->sendMessage($to, $message); + } + + protected function sendMessage($recipient, UltraSmsMessage $message) + { + $message->content = html_entity_decode($message->content, ENT_QUOTES, 'utf-8'); + $message->content = urlencode($message->content); + + //the sms format must start with 6 + $valid_mobile = ''; + + if ($recipient[0] == '6') { + $valid_mobile = '+'.$recipient; + } + + if ($recipient[0] == '0') { + $valid_mobile = '+6'.$recipient; + } + + $params = [ + 'to' => $valid_mobile, + 'body' => $message->content, + ]; + + if ($message->sendAt instanceof \DateTimeInterface) { + $params['time'] = '0'.$message->sendAt->getTimestamp(); + } + + $this->ultrasms->send($params); + } +} diff --git a/src/UltraSmsMessage.php b/src/UltraSmsMessage.php new file mode 100644 index 0000000..2cc2b38 --- /dev/null +++ b/src/UltraSmsMessage.php @@ -0,0 +1,68 @@ +content = $content; + } + + /** + * Set the message content. + * + * @param string $content + * + * @return $this + */ + public function content($content) + { + $this->content = $content; + + return $this; + } + + /** + * Set the time the message should be sent. + * + * @param \DateTimeInterface|null $sendAt + * + * @return $this + */ + public function sendAt(\DateTimeInterface $sendAt = null) + { + $this->sendAt = $sendAt; + + return $this; + } +} diff --git a/src/UltraSmsServiceProvider.php b/src/UltraSmsServiceProvider.php new file mode 100644 index 0000000..a9d0032 --- /dev/null +++ b/src/UltraSmsServiceProvider.php @@ -0,0 +1,17 @@ +app->singleton(UltraSmsApi::class, function () { + $config = config('services.ultrasms'); + + return new UltraSmsApi($config); + }); + } +} diff --git a/tests/UltraSmsChannelTest.php b/tests/UltraSmsChannelTest.php new file mode 100644 index 0000000..3c45f82 --- /dev/null +++ b/tests/UltraSmsChannelTest.php @@ -0,0 +1,128 @@ + 'instaceId', + 'token' => 'token', + ]; + + $this->ultrasms = M::mock(UltraSmsApi::class, $config); + $this->channel = new UltraSmsChannel($this->ultrasms); + $this->message = M::mock(UltraSmsMessage::class); + } + + public function tearDown() + { + M::close(); + + parent::tearDown(); + } + + /** @test */ + public function it_can_send_a_notification() + { + $this->ultrasms->shouldReceive('send')->once() + ->with( + [ + 'to' => '60123456789', + 'body' => 'hello', + ] + ); + + $this->channel->send(new TestNotifiable(), new TestNotification()); + } + + /** @test */ + public function it_can_send_a_deferred_notification() + { + self::$sendAt = new \DateTime(); + + $this->ultrasms->shouldReceive('send')->once() + ->with( + [ + 'to' => '60123456789', + 'body' => 'hello', + 'time' => '0'.self::$sendAt->getTimestamp(), + ] + ); + + $this->channel->send(new TestNotifiable(), new TestNotificationWithSendAt()); + } + + /** @test */ + public function it_does_not_send_a_message_when_to_missed() + { + $this->expectException(CouldNotSendNotification::class); + + $this->channel->send( + new TestNotifiableWithoutRouteNotificationForSmscru(), new TestNotification() + ); + } +} + +class TestNotifiable +{ + public function routeNotificationFor() + { + return '0123456789'; + } +} + +class TestNotifiableWithoutRouteNotificationForSmscru extends TestNotifiable +{ + public function routeNotificationFor() + { + return false; + } +} + +class TestNotification extends Notification +{ + public function toUltraSms() + { + return UltraSmsMessage::create('hello'); + } +} + +class TestNotificationWithSendAt extends Notification +{ + public function toUltraSms() + { + return UltraSmsMessage::create('hello') + ->sendAt(UltraSmsChannelTest::$sendAt); + } +} diff --git a/tests/UltraSmsMessageTest.php b/tests/UltraSmsMessageTest.php new file mode 100644 index 0000000..dc99031 --- /dev/null +++ b/tests/UltraSmsMessageTest.php @@ -0,0 +1,41 @@ +assertEquals('hello', $message->content); + } + + /** @test */ + public function it_can_accept_a_content_when_creating_a_message() + { + $message = UltraSmsMessage::create('hello'); + + $this->assertEquals('hello', $message->content); + } + + /** @test */ + public function it_can_set_the_content() + { + $message = (new UltraSmsMessage())->content('hello'); + + $this->assertEquals('hello', $message->content); + } + + /** @test */ + public function it_can_set_the_send_at() + { + $sendAt = date_create(); + $message = (new UltraSmsMessage())->sendAt($sendAt); + + $this->assertEquals($sendAt, $message->sendAt); + } +}