├── .github ├── ISSUE_TEMPLATE.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── build.yml ├── LICENSE.txt ├── composer.json ├── config └── nexmo.php └── src ├── Facade └── Nexmo.php └── NexmoServiceProvider.php /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Expected Behavior 4 | 5 | 6 | 7 | ## Current Behavior 8 | 9 | 10 | 11 | ## Possible Solution 12 | 13 | 14 | 15 | ## Steps to Reproduce (for bugs) 16 | 17 | 18 | 1. 19 | 2. 20 | 3. 21 | 4. 22 | 23 | ## Context 24 | 25 | 26 | 27 | ## Your Environment 28 | 29 | * Version used: 30 | * Environment name and version (e.g. PHP 7.2 on nginx 1.19.1): 31 | * Operating System and version: 32 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Description 4 | 5 | 6 | ## Motivation and Context 7 | 8 | 9 | 10 | ## How Has This Been Tested? 11 | 12 | 13 | 14 | 15 | ## Example Output or Screenshots (if appropriate): 16 | 17 | ## Types of changes 18 | 19 | - [ ] Bug fix (non-breaking change which fixes an issue) 20 | - [ ] New feature (non-breaking change which adds functionality) 21 | - [ ] Breaking change (fix or feature that would cause existing functionality to change) 22 | 23 | ## Checklist: 24 | 25 | 26 | - [ ] My code follows the code style of this project. 27 | - [ ] My change requires a change to the documentation. 28 | - [ ] I have updated the documentation accordingly. 29 | - [ ] I have read the **CONTRIBUTING** document. 30 | - [ ] I have added tests to cover my changes. 31 | - [ ] All new and existing tests passed. 32 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: build 3 | 4 | on: [ push, pull_request ] 5 | 6 | jobs: 7 | build: 8 | runs-on: ubuntu-latest 9 | strategy: 10 | matrix: 11 | php: [ '7.4', '8.0', '8.1' ] 12 | continue-on-error: ${{ matrix.php == '8.1' }} 13 | name: PHP ${{ matrix.php }} Test 14 | 15 | steps: 16 | - name: Checkout 17 | uses: actions/checkout@v2 18 | 19 | - name: Setup PHP 20 | uses: shivammathur/setup-php@v2 21 | with: 22 | php-version: ${{ matrix.php }} 23 | extensions: json, mbstring 24 | coverage: pcov 25 | env: 26 | COMPOSER_TOKEN: ${{ secrets.GITHUB_TOKEN }} 27 | 28 | - name: Setup problem matchers for PHPUnit 29 | run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" 30 | 31 | - name: Get Composer cache directory 32 | id: composercache 33 | run: echo "::set-output name=dir::$(composer config cache-files-dir)" 34 | 35 | - name: Cache Composer dependencies 36 | uses: actions/cache@v2 37 | with: 38 | path: ${{ steps.composercache.outputs.dir }} 39 | key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} 40 | restore-keys: ${{ runner.os }}-composer- 41 | 42 | - name: Install dependencies 43 | run: composer update --prefer-dist --no-interaction 44 | 45 | - name: Analyze & test 46 | run: composer test -- -v --coverage-clover=coverage.xml 47 | 48 | - name: Run codecov 49 | uses: codecov/codecov-action@v1 50 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2017 Nexmo, Inc 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the Software without restriction, including without limitation the 6 | rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit 7 | persons to whom the Software is furnished to do so, subject to the following conditions: 8 | 9 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the 10 | Software. 11 | 12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 13 | WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 14 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 15 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nexmo/laravel", 3 | "description": "Laravel Package for Nexmo's PHP Client", 4 | "license": "MIT", 5 | "type": "library", 6 | "authors": [ 7 | { 8 | "name": "James Seconde", 9 | "role": "PHP Developer Advocate", 10 | "email": "jim.seconde@vonage.com" 11 | }, 12 | { 13 | "name": "Tim Lytle", 14 | "role": "Developer", 15 | "homepage": "http://twitter.com/tjlytle" 16 | }, 17 | { 18 | "name": "Michael Heap", 19 | "role": "Developer", 20 | "homepage": "http://twitter.com/mheap" 21 | }, 22 | { 23 | "name": "Chris Tankersley", 24 | "role": "Developer", 25 | "homepage": "http://twitter.com/dragonmantank" 26 | } 27 | ], 28 | "support": { 29 | "email": "devrel@nexmo.com" 30 | }, 31 | "require": { 32 | "php": "^7.4|^8.0|^8.1", 33 | "illuminate/support": "^5.2|^6.0|^7.0|^8.0", 34 | "vonage/client": "^3.0" 35 | }, 36 | "require-dev": { 37 | "phpunit/phpunit": "^5.3|~6.0|~8.0|~9.0", 38 | "orchestra/testbench": "~3.0|^4.0|^5.0|^6.0" 39 | }, 40 | "autoload": { 41 | "psr-4": { 42 | "Nexmo\\Laravel\\": "src/" 43 | } 44 | }, 45 | "autoload-dev": { 46 | "psr-4": { 47 | "Nexmo\\Laravel\\Tests\\": "tests/" 48 | } 49 | }, 50 | "extra": { 51 | "laravel": { 52 | "providers": [ 53 | "Nexmo\\Laravel\\NexmoServiceProvider" 54 | ], 55 | "aliases": { 56 | "Nexmo": "Nexmo\\Laravel\\Facade\\Nexmo" 57 | } 58 | } 59 | }, 60 | "scripts": { 61 | "test": "phpunit" 62 | }, 63 | "config": { 64 | "allow-plugins": { 65 | "kylekatarnls/update-helper": true 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /config/nexmo.php: -------------------------------------------------------------------------------- 1 | function_exists('env') ? env('NEXMO_KEY', '') : '', 16 | 'api_secret' => function_exists('env') ? env('NEXMO_SECRET', '') : '', 17 | 18 | /* 19 | |-------------------------------------------------------------------------- 20 | | Signature Secret 21 | |-------------------------------------------------------------------------- 22 | | 23 | | If you're using a signature secret, use this section. This can be used 24 | | without an `api_secret` for some APIs, as well as with an `api_secret` 25 | | for all APIs. 26 | | 27 | */ 28 | 29 | 'signature_secret' => function_exists('env') ? env('NEXMO_SIGNATURE_SECRET', '') : '', 30 | 31 | /* 32 | |-------------------------------------------------------------------------- 33 | | Private Key 34 | |-------------------------------------------------------------------------- 35 | | 36 | | Private keys are used to generate JWTs for authentication. Generation is 37 | | handled by the library. JWTs are required for newer APIs, such as voice 38 | | and media 39 | | 40 | */ 41 | 42 | 'private_key' => function_exists('env') ? env('NEXMO_PRIVATE_KEY', '') : '', 43 | 'application_id' => function_exists('env') ? env('NEXMO_APPLICATION_ID', '') : '', 44 | 45 | /* 46 | |-------------------------------------------------------------------------- 47 | | Application Identifiers 48 | |-------------------------------------------------------------------------- 49 | | 50 | | Add an application name and version here to identify your application when 51 | | making API calls 52 | | 53 | */ 54 | 55 | 'app' => ['name' => function_exists('env') ? env('NEXMO_APP_NAME', 'NexmoLaravel') : 'NexmoLaravel', 56 | 'version' => function_exists('env') ? env('NEXMO_APP_VERSION', '1.1.2') : '1.1.2'], 57 | 58 | /* 59 | |-------------------------------------------------------------------------- 60 | | Client Override 61 | |-------------------------------------------------------------------------- 62 | | 63 | | In the event you need to use this with nexmo/client-core, this can be set 64 | | to provide a custom HTTP client. 65 | | 66 | */ 67 | 68 | 'http_client' => function_exists('env') ? env('NEXMO_HTTP_CLIENT', '') : '', 69 | ]; 70 | -------------------------------------------------------------------------------- /src/Facade/Nexmo.php: -------------------------------------------------------------------------------- 1 | publishes([ 35 | $dist => config_path('nexmo.php'), 36 | ]); 37 | } 38 | 39 | // Merge config. 40 | $this->mergeConfigFrom($dist, 'nexmo'); 41 | } 42 | 43 | /** 44 | * Register the application services. 45 | * 46 | * @return void 47 | */ 48 | public function register() 49 | { 50 | // Bind Nexmo Client in Service Container. 51 | $this->app->singleton(Client::class, function ($app) { 52 | return $this->createNexmoClient($app['config']); 53 | }); 54 | $this->app->singleton(NexmoClient::class, function ($app) { 55 | return $this->createNexmoClient($app['config']); 56 | }); 57 | } 58 | 59 | /** 60 | * Get the services provided by the provider. 61 | * 62 | * @return array 63 | */ 64 | public function provides() 65 | { 66 | return [ 67 | Client::class, 68 | NexmoClient::class 69 | ]; 70 | } 71 | 72 | /** 73 | * Create a new Nexmo Client. 74 | * 75 | * @param Config $config 76 | * 77 | * @return Client 78 | * 79 | * @throws \RuntimeException 80 | */ 81 | protected function createNexmoClient(Config $config) 82 | { 83 | // Check for Nexmo config file. 84 | if (! $this->hasNexmoConfigSection()) { 85 | $this->raiseRunTimeException('Missing nexmo configuration section.'); 86 | } 87 | 88 | // Get Client Options. 89 | $options = array_diff_key($config->get('nexmo'), ['private_key', 'application_id', 'api_key', 'api_secret', 'shared_secret', 'app']); 90 | 91 | // Do we have a private key? 92 | $privateKeyCredentials = null; 93 | if ($this->nexmoConfigHas('private_key')) { 94 | if ($this->nexmoConfigHasNo('application_id')) { 95 | $this->raiseRunTimeException('You must provide nexmo.application_id when using a private key'); 96 | } 97 | 98 | $privateKeyCredentials = $this->createPrivateKeyCredentials($config->get('nexmo.private_key'), $config->get('nexmo.application_id')); 99 | } 100 | 101 | $basicCredentials = null; 102 | if ($this->nexmoConfigHas('api_secret')) { 103 | $basicCredentials = $this->createBasicCredentials($config->get('nexmo.api_key'), $config->get('nexmo.api_secret')); 104 | } 105 | 106 | $signatureCredentials = null; 107 | if ($this->nexmoConfigHas('signature_secret')) { 108 | $signatureCredentials = $this->createSignatureCredentials($config->get('nexmo.api_key'), $config->get('nexmo.signature_secret')); 109 | } 110 | 111 | // We can have basic only, signature only, private key only or 112 | // we can have private key + basic/signature, so let's work out 113 | // what's been provided 114 | if ($basicCredentials && $signatureCredentials) { 115 | $this->raiseRunTimeException('Provide either nexmo.api_secret or nexmo.signature_secret'); 116 | } 117 | 118 | if ($privateKeyCredentials && $basicCredentials) { 119 | $credentials = new Client\Credentials\Container( 120 | $privateKeyCredentials, 121 | $basicCredentials 122 | ); 123 | } else if ($privateKeyCredentials && $signatureCredentials) { 124 | $credentials = new Client\Credentials\Container( 125 | $privateKeyCredentials, 126 | $signatureCredentials 127 | ); 128 | } else if ($privateKeyCredentials) { 129 | $credentials = $privateKeyCredentials; 130 | } else if ($signatureCredentials) { 131 | $credentials = $signatureCredentials; 132 | } else if ($basicCredentials) { 133 | $credentials = $basicCredentials; 134 | } else { 135 | $possibleNexmoKeys = [ 136 | 'api_key + api_secret', 137 | 'api_key + signature_secret', 138 | 'private_key + application_id', 139 | 'api_key + api_secret + private_key + application_id', 140 | 'api_key + signature_secret + private_key + application_id', 141 | ]; 142 | $this->raiseRunTimeException( 143 | 'Please provide Nexmo API credentials. Possible combinations: ' 144 | . join(", ", $possibleNexmoKeys) 145 | ); 146 | } 147 | 148 | $httpClient = null; 149 | if ($this->nexmoConfigHas('http_client')) { 150 | $httpClient = $this->app->make($config->get(('nexmo.http_client'))); 151 | } 152 | 153 | return new Client($credentials, $options, $httpClient); 154 | } 155 | 156 | /** 157 | * Checks if has global Nexmo configuration section. 158 | * 159 | * @return bool 160 | */ 161 | protected function hasNexmoConfigSection() 162 | { 163 | return $this->app->make(Config::class) 164 | ->has('nexmo'); 165 | } 166 | 167 | /** 168 | * Checks if Nexmo config does not 169 | * have a value for the given key. 170 | * 171 | * @param string $key 172 | * 173 | * @return bool 174 | */ 175 | protected function nexmoConfigHasNo($key) 176 | { 177 | return ! $this->nexmoConfigHas($key); 178 | } 179 | 180 | /** 181 | * Checks if Nexmo config has value for the 182 | * given key. 183 | * 184 | * @param string $key 185 | * 186 | * @return bool 187 | */ 188 | protected function nexmoConfigHas($key) 189 | { 190 | /** @var Config $config */ 191 | $config = $this->app->make(Config::class); 192 | 193 | // Check for Nexmo config file. 194 | if (! $config->has('nexmo')) { 195 | return false; 196 | } 197 | 198 | return 199 | $config->has('nexmo.'.$key) && 200 | ! is_null($config->get('nexmo.'.$key)) && 201 | ! empty($config->get('nexmo.'.$key)); 202 | } 203 | 204 | /** 205 | * Create a Basic credentials for client. 206 | * 207 | * @param string $key 208 | * @param string $secret 209 | * 210 | * @return Client\Credentials\Basic 211 | */ 212 | protected function createBasicCredentials($key, $secret) 213 | { 214 | return new Client\Credentials\Basic($key, $secret); 215 | } 216 | 217 | /** 218 | * Create SignatureSecret credentials for client. 219 | * 220 | * @param string $key 221 | * @param string $signatureSecret 222 | * 223 | * @return Client\Credentials\SignatureSecret 224 | */ 225 | protected function createSignatureCredentials($key, $signatureSecret) 226 | { 227 | return new Client\Credentials\SignatureSecret($key, $signatureSecret); 228 | } 229 | 230 | /** 231 | * Create Keypair credentials for client. 232 | * 233 | * @param string $key 234 | * @param string $applicationId 235 | * 236 | * @return Client\Credentials\Keypair 237 | */ 238 | protected function createPrivateKeyCredentials($key, $applicationId) 239 | { 240 | return new Client\Credentials\Keypair($this->loadPrivateKey($key), $applicationId); 241 | } 242 | 243 | /** 244 | * Load private key contents from root directory 245 | */ 246 | protected function loadPrivateKey($key) 247 | { 248 | if (app()->runningUnitTests()) { 249 | return '===FAKE-KEY==='; 250 | } 251 | 252 | if (Str::startsWith($key, '-----BEGIN PRIVATE KEY-----')) { 253 | return $key; 254 | } 255 | 256 | // If it's a relative path, start searching in the 257 | // project root 258 | if ($key[0] !== '/') { 259 | $key = base_path().'/'.$key; 260 | } 261 | 262 | return file_get_contents($key); 263 | } 264 | 265 | /** 266 | * Raises Runtime exception. 267 | * 268 | * @param string $message 269 | * 270 | * @throws \RuntimeException 271 | */ 272 | protected function raiseRunTimeException($message) 273 | { 274 | throw new \RuntimeException($message); 275 | } 276 | } 277 | --------------------------------------------------------------------------------