├── .styleci.yml ├── .editorconfig ├── CHANGELOG.md ├── src ├── Mail │ ├── TransportManager.php │ └── Transport │ │ └── SmartDoveTransport.php ├── SMS │ ├── SmartDoveChannel.php │ ├── SmartDoveMessage.php │ └── Client.php └── SmartDoveServiceProvider.php ├── ISSUE_TEMPLATE.md ├── phpunit.xml ├── LICENSE.md ├── CONTRIBUTING.md ├── PULL_REQUEST_TEMPLATE.md ├── composer.json └── CODE_OF_CONDUCT.md /.styleci.yml: -------------------------------------------------------------------------------- 1 | preset: psr2 2 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | ; This file is for unifying the coding style for different editors and IDEs. 2 | ; More information at http://editorconfig.org 3 | 4 | root = true 5 | 6 | [*] 7 | charset = utf-8 8 | indent_size = 4 9 | indent_style = space 10 | end_of_line = lf 11 | insert_final_newline = true 12 | trim_trailing_whitespace = true 13 | 14 | [*.md] 15 | trim_trailing_whitespace = false 16 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to `:package_name` will be documented in this file. 4 | 5 | Updates should follow the [Keep a CHANGELOG](http://keepachangelog.com/) principles. 6 | 7 | ## NEXT - YYYY-MM-DD 8 | 9 | ### Added 10 | - Nothing 11 | 12 | ### Deprecated 13 | - Nothing 14 | 15 | ### Fixed 16 | - Nothing 17 | 18 | ### Removed 19 | - Nothing 20 | 21 | ### Security 22 | - Nothing 23 | -------------------------------------------------------------------------------- /src/Mail/TransportManager.php: -------------------------------------------------------------------------------- 1 | app['config']->get('services.smartdove.mail', []); 18 | 19 | return new SmartDoveTransport( 20 | $this->guzzle($config), 21 | $config['token'] 22 | ); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Detailed description 4 | 5 | Provide a detailed description of the change or addition you are proposing. 6 | 7 | Make it clear if the issue is a bug, an enhancement or just a question. 8 | 9 | ## Context 10 | 11 | Why is this change important to you? How would you use it? 12 | 13 | How can it benefit other users? 14 | 15 | ## Possible implementation 16 | 17 | Not obligatory, but suggest an idea for implementing addition or change. 18 | 19 | ## Your environment 20 | 21 | Include as many relevant details about the environment you experienced the bug in and how to reproduce it. 22 | 23 | * Version used (e.g. PHP 5.6, HHVM 3): 24 | * Operating system and version (e.g. Ubuntu 16.04, Windows 7): 25 | * Link to your project: 26 | * ... 27 | * ... 28 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | tests 13 | 14 | 15 | 16 | 17 | 18 | src 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Laravel 道場 (Laravel Dojo) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/SMS/SmartDoveChannel.php: -------------------------------------------------------------------------------- 1 | client = $client; 24 | } 25 | 26 | /** 27 | * Send the given notification. 28 | * 29 | * @param mixed $notifiable 30 | * @param \Illuminate\Notifications\Notification $notification 31 | * @return \Meditate\SmartDove\SMS\SmartDoveMessage 32 | */ 33 | public function send($notifiable, Notification $notification) 34 | { 35 | if (! $to = $notifiable->routeNotificationFor('smartdove')) { 36 | return; 37 | } 38 | 39 | $message = $notification->toSmartDove($notifiable); 40 | 41 | if (is_string($message)) { 42 | $message = new SmartDoveMessage($message); 43 | } 44 | 45 | return $this->client->send([ 46 | 'phone_number' => $to, 47 | 'content' => trim($message->content), 48 | ]); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Contributions are **welcome** and will be fully **credited**. 4 | 5 | We accept contributions via Pull Requests on [Github](https://github.com/:vendor/:package_name). 6 | 7 | 8 | ## Pull Requests 9 | 10 | - **[PSR-2 Coding Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)** - Check the code style with ``$ composer check-style`` and fix it with ``$ composer fix-style``. 11 | 12 | - **Add tests!** - Your patch won't be accepted if it doesn't have tests. 13 | 14 | - **Document any change in behaviour** - Make sure the `README.md` and any other relevant documentation are kept up-to-date. 15 | 16 | - **Consider our release cycle** - We try to follow [SemVer v2.0.0](http://semver.org/). Randomly breaking public APIs is not an option. 17 | 18 | - **Create feature branches** - Don't ask us to pull from your master branch. 19 | 20 | - **One pull request per feature** - If you want to do more than one thing, send multiple pull requests. 21 | 22 | - **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. 23 | 24 | 25 | ## Running Tests 26 | 27 | ``` bash 28 | $ composer test 29 | ``` 30 | 31 | 32 | **Happy coding**! 33 | -------------------------------------------------------------------------------- /src/SmartDoveServiceProvider.php: -------------------------------------------------------------------------------- 1 | app->singleton(Client::class, function ($app) { 22 | $token = $this->app['config']->get('services.smartdove.sms.token', ''); 23 | 24 | return new Client($token); 25 | }); 26 | } 27 | 28 | /** 29 | * Register the Swift Transport instance. 30 | * 31 | * @return void 32 | */ 33 | public function registerSwiftTransport() 34 | { 35 | $this->app->singleton('swift.transport', function ($app) { 36 | return new TransportManager($app); 37 | }); 38 | } 39 | 40 | /** 41 | * Get the services provided by the provider. 42 | * 43 | * @return array 44 | */ 45 | public function provides() 46 | { 47 | return [ 48 | 'mailer', 49 | 'swift.mailer', 50 | 'swift.transport', 51 | Markdown::class, 52 | Client::class, 53 | ]; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/SMS/SmartDoveMessage.php: -------------------------------------------------------------------------------- 1 | content = $content; 36 | } 37 | 38 | /** 39 | * Set the message subject. 40 | * 41 | * @param string $subject 42 | * @return $this 43 | */ 44 | public function subject($subject) 45 | { 46 | $this->subject = $subject; 47 | 48 | return $this; 49 | } 50 | 51 | /** 52 | * Set the message subject. 53 | * 54 | * @param string $content 55 | * @return $this 56 | */ 57 | public function content($content) 58 | { 59 | $this->content = $content; 60 | 61 | return $this; 62 | } 63 | 64 | /** 65 | * Set the message send time. 66 | * 67 | * @param \Carbon\Carbon|string $sendTime 68 | * @return $this 69 | */ 70 | public function sendTime($sendTime) 71 | { 72 | $this->sendTime = $sendTime; 73 | 74 | return $this; 75 | } 76 | 77 | /** 78 | * Create a new message instance. 79 | * 80 | * @param string $content 81 | * @return static 82 | */ 83 | public static function create($content) 84 | { 85 | return new static($content); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Description 4 | 5 | Describe your changes in detail. 6 | 7 | ## Motivation and context 8 | 9 | Why is this change required? What problem does it solve? 10 | 11 | If it fixes an open issue, please link to the issue here (if you write `fixes #num` 12 | or `closes #num`, the issue will be automatically closed when the pull is accepted.) 13 | 14 | ## How has this been tested? 15 | 16 | Please describe in detail how you tested your changes. 17 | 18 | Include details of your testing environment, and the tests you ran to 19 | see how your change affects other areas of the code, etc. 20 | 21 | ## Screenshots (if appropriate) 22 | 23 | ## Types of changes 24 | 25 | What types of changes does your code introduce? Put an `x` in all the boxes that apply: 26 | - [ ] Bug fix (non-breaking change which fixes an issue) 27 | - [ ] New feature (non-breaking change which adds functionality) 28 | - [ ] Breaking change (fix or feature that would cause existing functionality to change) 29 | 30 | ## Checklist: 31 | 32 | Go over all the following points, and put an `x` in all the boxes that apply. 33 | 34 | Please, please, please, don't send your pull request until all of the boxes are ticked. Once your pull request is created, it will trigger a build on our [continuous integration](http://www.phptherightway.com/#continuous-integration) server to make sure your [tests and code style pass](https://help.github.com/articles/about-required-status-checks/). 35 | 36 | - [ ] I have read the **[CONTRIBUTING](CONTRIBUTING.md)** document. 37 | - [ ] My pull request addresses exactly one patch/feature. 38 | - [ ] I have created a branch for this patch/feature. 39 | - [ ] Each individual commit in the pull request is meaningful. 40 | - [ ] I have added tests to cover my changes. 41 | - [ ] If my change requires a change to the documentation, I have updated it accordingly. 42 | 43 | If you're unsure about any of these, don't hesitate to ask. We're here to help! 44 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "meditate/smartdove", 3 | "description": "SMARTDOVE SMTP & SMS SDK for PHP/Laravel", 4 | "keywords": [ 5 | "sms", 6 | "mail", 7 | "smtp", 8 | "laravel", 9 | "notification", 10 | "smartdove", 11 | "taiwan", 12 | "laravel-dojo", 13 | "meditate" 14 | ], 15 | "type": "library", 16 | "license": "MIT", 17 | "authors": [ 18 | { 19 | "name": "Shengyou Fan", 20 | "email": "shengyoufan@gmail.com" 21 | }, 22 | { 23 | "name": "Wei-Cheng Shi", 24 | "email": "tentail10@gmail.com" 25 | } 26 | ], 27 | "minimum-stability": "stable", 28 | "require": { 29 | "php": "^7.0", 30 | "nesbot/carbon": "^1.22", 31 | "php-http/client-implementation": "^1.0", 32 | "php-http/discovery": "^1.4", 33 | "php-http/message": "^1.6" 34 | }, 35 | "require-dev": { 36 | "illuminate/mail": "^5.6", 37 | "illuminate/support": "^5.6", 38 | "php-http/guzzle6-adapter": "^1.1", 39 | "phpunit/phpunit": "^7.2", 40 | "squizlabs/php_codesniffer": "^3.3", 41 | "vlucas/phpdotenv": "^2.4" 42 | }, 43 | "autoload": { 44 | "psr-4": { 45 | "Meditate\\SmartDove\\": "src/" 46 | } 47 | }, 48 | "autoload-dev": { 49 | "psr-4": { 50 | "Meditate\\SmartDove\\Tests\\": "tests/" 51 | } 52 | }, 53 | "scripts": { 54 | "test": "phpunit", 55 | "check-style": "phpcs -p --standard=PSR2 --runtime-set ignore_errors_on_exit 1 --runtime-set ignore_warnings_on_exit 1 src tests", 56 | "fix-style": "phpcbf -p --standard=PSR2 --runtime-set ignore_errors_on_exit 1 --runtime-set ignore_warnings_on_exit 1 src tests" 57 | }, 58 | "config": { 59 | "preferred-install": "dist", 60 | "sort-packages": true, 61 | "optimize-autoloader": true 62 | }, 63 | "extra": { 64 | "laravel": { 65 | "providers": [ 66 | "Meditate\\SmartDove\\SmartDoveServiceProvider" 67 | ] 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to make participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at `:author_email`. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at [http://contributor-covenant.org/version/1/4][version] 72 | 73 | [homepage]: http://contributor-covenant.org 74 | [version]: http://contributor-covenant.org/version/1/4/ 75 | -------------------------------------------------------------------------------- /src/Mail/Transport/SmartDoveTransport.php: -------------------------------------------------------------------------------- 1 | token = $token; 35 | $this->client = $client; 36 | } 37 | 38 | /** 39 | * {@inheritdoc} 40 | */ 41 | public function send(Swift_Mime_SimpleMessage $message, &$failedRecipients = null) 42 | { 43 | $this->beforeSendPerformed($message); 44 | 45 | $from = $this->getFrom($message); 46 | 47 | $to = $this->getTo($message); 48 | 49 | $message->setBcc([]); 50 | 51 | $message->getHeaders()->addTextHeader( 52 | 'content-type', 'application/x-www-form-urlencoded' 53 | ); 54 | 55 | $this->client->post('https://api.smartdove.net/index.php?r=api/SendMail', [ 56 | 'form_params' => $this->payload($message, $from, $to), 57 | ]); 58 | 59 | $this->sendPerformed($message); 60 | 61 | return $this->numberOfRecipients($message); 62 | } 63 | 64 | /** 65 | * Get the HTTP payload for sending the Mailgun message. 66 | * 67 | * @param \Swift_Mime_SimpleMessage $message 68 | * @param array $from 69 | * @param array $to 70 | * @return array 71 | */ 72 | protected function payload(Swift_Mime_SimpleMessage $message, $from, $to) 73 | { 74 | return [ 75 | 'token' => $this->getToken(), 76 | 'data' => json_encode([ 77 | 'From' => $from, 78 | 'To' => $to, 79 | 'Subject' => $message->getSubject(), 80 | 'HTML' => $message->getBody(), 81 | ]), 82 | ]; 83 | } 84 | 85 | /** 86 | * Get the "from" payload field for the API request. 87 | * 88 | * @param \Swift_Mime_SimpleMessage $message 89 | * @return array 90 | */ 91 | protected function getFrom(Swift_Mime_SimpleMessage $message) 92 | { 93 | $from = $message->getFrom(); 94 | 95 | $form_email = key($from); 96 | $form_name = $from[$form_email] ?? null; 97 | 98 | return [ 99 | 'Email' => $form_email, 100 | 'Name' => $form_name, 101 | ]; 102 | } 103 | 104 | /** 105 | * Get the "to" payload field for the API request. 106 | * 107 | * @param \Swift_Mime_SimpleMessage $message 108 | * @return array 109 | */ 110 | protected function getTo(Swift_Mime_SimpleMessage $message) 111 | { 112 | return collect($this->allContacts($message))->map(function ($display, $address) { 113 | return ['Email' => $address]; 114 | })->values()->toArray(); 115 | } 116 | 117 | /** 118 | * Get all of the contacts for the message. 119 | * 120 | * @param \Swift_Mime_SimpleMessage $message 121 | * @return array 122 | */ 123 | protected function allContacts(Swift_Mime_SimpleMessage $message) 124 | { 125 | return array_merge( 126 | (array) $message->getTo(), (array) $message->getCc(), (array) $message->getBcc() 127 | ); 128 | } 129 | 130 | /** 131 | * Get the API token being used by the transport. 132 | * 133 | * @return string 134 | */ 135 | public function getToken() 136 | { 137 | return $this->token; 138 | } 139 | 140 | /** 141 | * Set the API token being used by the transport. 142 | * 143 | * @param string $token 144 | * @return string 145 | */ 146 | public function setToken($token) 147 | { 148 | return $this->token = $token; 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /src/SMS/Client.php: -------------------------------------------------------------------------------- 1 | token = $token; 55 | $this->httpClient = $httpClient ? : HttpClientDiscovery::find(); 56 | $this->messageFactory = $messageFactory ? : MessageFactoryDiscovery::find(); 57 | } 58 | 59 | /** 60 | * @param mixed $to 61 | * @return Client 62 | */ 63 | public function to($to): Client 64 | { 65 | $this->receivers = array_merge($this->receivers, is_array($to) ? $to : func_get_args()); 66 | 67 | return $this; 68 | } 69 | 70 | /** 71 | * @param string $text 72 | * @return Client 73 | */ 74 | public function text(string $text): Client 75 | { 76 | $this->text = $text; 77 | 78 | return $this; 79 | } 80 | 81 | /** 82 | * @param array $params 83 | * @return array 84 | */ 85 | public function send($params = null): array 86 | { 87 | if (count($this->receivers) > 1 || ! is_null($params)) { 88 | $apiEndPoint = $this->batchApiEndpoint; 89 | $apiParams = $this->buildBatchParams($params); 90 | } else { 91 | $apiEndPoint = $this->apiEndpoint; 92 | $apiParams = $this->buildParams($params); 93 | } 94 | 95 | $request = $this->messageFactory->createRequest( 96 | 'POST', 97 | $apiEndPoint, 98 | ['Content-Type' => 'application/x-www-form-urlencoded; charset=utf-8'], 99 | $apiParams 100 | ); 101 | 102 | $response = $this->httpClient->sendRequest($request); 103 | 104 | $this->receivers = []; 105 | 106 | return json_decode($response->getBody()->getContents(), true); 107 | } 108 | 109 | /** 110 | * @param array $params 111 | * @return string 112 | */ 113 | private function buildParams(array $params = null): string 114 | { 115 | $config = [ 116 | 'token' => $this->token, 117 | 'phone_number' => $this->receivers[0], 118 | 'content' => $this->text, 119 | ]; 120 | 121 | if (! is_null($params)) { 122 | $config = array_merge($config, $params); 123 | } 124 | 125 | return http_build_query($config); 126 | } 127 | 128 | /** 129 | * @param array $params 130 | * @return string 131 | */ 132 | private function buildBatchParams(array $params = null): string 133 | { 134 | $config = [ 135 | 'token' => $this->token, 136 | ]; 137 | 138 | $data = []; 139 | foreach ($this->receivers as $receiver) { 140 | $data[] = [ 141 | 'id' => str_random(7), 142 | 'phone_number' => $receiver, 143 | 'content' => $this->text, 144 | ]; 145 | } 146 | 147 | if (! is_null($params)) { 148 | if (is_array($params['phone_number'])) { 149 | foreach ($params['phone_number'] as $number) { 150 | $data[] = [ 151 | 'id' => str_random(7), 152 | 'phone_number' => $number, 153 | 'content' => $params['content'], 154 | ]; 155 | } 156 | } else { 157 | $data[] = [ 158 | 'id' => str_random(7), 159 | 'phone_number' => $params['phone_number'], 160 | 'content' => $params['content'], 161 | ]; 162 | } 163 | } 164 | 165 | $config['data'] = json_encode($data); 166 | 167 | return http_build_query($config); 168 | } 169 | } 170 | --------------------------------------------------------------------------------