├── examples ├── general.php ├── init.php ├── whois.php ├── hlr.php ├── sms.php ├── env.php ├── viber.php ├── voice.php ├── vk.php ├── whatsapp.php ├── social.php ├── token.php ├── call.php └── account.php ├── src ├── Constants.php ├── IdeHelper │ ├── Limits.php │ ├── Webhook.php │ ├── Blacklist.php │ ├── Whitelist.php │ └── Account.php ├── Api │ ├── MethodInvoker.php │ ├── Module.php │ ├── ModuleLoader.php │ ├── Schema.php │ └── Modules.php ├── Utils │ ├── Version.php │ ├── Validator.php │ ├── Url.php │ └── Helpers.php ├── Http │ ├── RestException.php │ └── RestClient.php └── GreenSMS.php ├── tests ├── TestCase.php ├── GeneralTest.php ├── Utility.php ├── EnvTest.php ├── TokenTest.php ├── VoiceTest.php ├── ViberTest.php ├── TelegramTest.php ├── RestClientTest.php ├── WhatsappTest.php ├── VkTest.php ├── HlrTest.php ├── PreSendHandlerTest.php ├── CallTest.php ├── SmsTest.php └── AccountTest.php ├── .github └── workflows │ ├── main.yml │ └── php.yml ├── composer.json ├── README.md ├── phpcs.xml └── .gitignore /examples/general.php: -------------------------------------------------------------------------------- 1 | status(); 5 | print_r($response->status); 6 | -------------------------------------------------------------------------------- /src/Constants.php: -------------------------------------------------------------------------------- 1 | 'test', 9 | 'pass' => 'test' 10 | ]); 11 | -------------------------------------------------------------------------------- /src/IdeHelper/Limits.php: -------------------------------------------------------------------------------- 1 | whois->lookup([ 5 | 'to' => '79260000000', 6 | ]); 7 | 8 | echo "Lookup Response"; 9 | print_r($response); 10 | echo "\n\n"; 11 | -------------------------------------------------------------------------------- /src/IdeHelper/Account.php: -------------------------------------------------------------------------------- 1 | $method)) { 12 | $func = $this->$method; 13 | return call_user_func_array($func, $args); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /examples/hlr.php: -------------------------------------------------------------------------------- 1 | hlr->send([ 5 | 'to' => '79150000000', 6 | 'txt' => '1221' 7 | ]); 8 | 9 | echo "Hlr Request Id: " . $response->request_id; 10 | echo "\n\n"; 11 | 12 | $response = $client->hlr->status([ 13 | 'id' => '70d296f5-ac52-403d-a27b-24829c2faebc', 14 | 'to' => '79150000000' 15 | ]); 16 | 17 | echo "Hlr Status: \n"; 18 | print_r($response); 19 | -------------------------------------------------------------------------------- /examples/sms.php: -------------------------------------------------------------------------------- 1 | sms->send([ 5 | 'to' => '79260000121', 6 | 'txt' => 'Here is your message for delivery' 7 | ]); 8 | 9 | echo "Sms Request Id: " . $response->request_id; 10 | echo "\n\n"; 11 | 12 | $response = $client->sms->status([ 13 | 'id' => 'dc2bac6d-f375-4e19-9a02-ef0148991635', 14 | ]); 15 | 16 | echo "Sms Status: \n"; 17 | print_r($response); 18 | -------------------------------------------------------------------------------- /tests/TestCase.php: -------------------------------------------------------------------------------- 1 | account->balance(); 13 | echo "Balance : " . $response->balance. "\n"; 14 | 15 | // Unsetting the env variable after use 16 | putenv('GREENSMS_USER'); 17 | putenv('GREENSMS_PASS'); 18 | -------------------------------------------------------------------------------- /examples/viber.php: -------------------------------------------------------------------------------- 1 | viber->send([ 5 | 'to' => '79260000121', 6 | 'txt' => 'Here is your message for delivery' 7 | ]); 8 | 9 | echo "Viber Request Id: " . $response->request_id; 10 | echo "\n\n"; 11 | 12 | $response = $client->viber->status([ 13 | 'id' => '0b18fab4-0c5d-4a8b-8ee4-057a59596c7d', 14 | ]); 15 | 16 | echo "Viber Status: \n"; 17 | print_r($response); 18 | -------------------------------------------------------------------------------- /examples/voice.php: -------------------------------------------------------------------------------- 1 | voice->send([ 5 | 'to' => '79260000121', 6 | 'txt' => '1221', 7 | 'language' => 'en', 8 | ]); 9 | 10 | echo "Voice Request Id: " . $response->request_id; 11 | echo "\n\n"; 12 | 13 | $response = $client->voice->status([ 14 | 'id' => '41f23094-deda-4cab-ac9c-3ab4f2fee9e6', 15 | ]); 16 | 17 | echo "Voice Status: \n"; 18 | print_r($response); 19 | -------------------------------------------------------------------------------- /examples/vk.php: -------------------------------------------------------------------------------- 1 | vk->send([ 5 | 'to' => '79260000121', 6 | 'txt' => '1221', 7 | 'from' => 'GreenSMS', 8 | 'cascade' => 'sms' 9 | ]); 10 | 11 | echo "VK Request Id: " . $response->request_id; 12 | echo "\n\n"; 13 | 14 | $response = $client->vk->status([ 15 | 'id' => 'caf3efb1-8aca-4387-9ed0-e667d315c5c9', 16 | ]); 17 | 18 | echo "VK Status: \n"; 19 | print_r($response); 20 | -------------------------------------------------------------------------------- /examples/whatsapp.php: -------------------------------------------------------------------------------- 1 | whatsapp->send([ 4 | 'to' => '79260000000', 5 | 'txt' => '1234 is your verification code.', 6 | 'from' => 'GREENSMS', 7 | 'tag' => 'test-sdk-node' 8 | ]); 9 | printf("WhatsApp Request ID: %s\n", $response->request_id); 10 | 11 | $response = $client->whatsapp->status(['id' => $response->request_id]); 12 | printf("WhatsApp Status: %s\n", var_export($response, 1)); 13 | -------------------------------------------------------------------------------- /examples/social.php: -------------------------------------------------------------------------------- 1 | social->send([ 5 | 'to' => '79260000121', 6 | 'txt' => 'Here is your message for delivery', 7 | 'from' => 'Test' 8 | ]); 9 | 10 | echo "Social Request Id: " . $response->request_id; 11 | echo "\n\n"; 12 | 13 | $response = $client->social->status([ 14 | 'id' => 'caf3efb1-8aca-4387-9ed0-e667d315c5c9', 15 | ]); 16 | 17 | echo "Social Status: \n"; 18 | print_r($response); 19 | -------------------------------------------------------------------------------- /examples/token.php: -------------------------------------------------------------------------------- 1 | $token 11 | ]); 12 | 13 | $response = $tokenClient->account->balance(); 14 | echo "Balance : " . $response->balance. "\n"; 15 | -------------------------------------------------------------------------------- /examples/call.php: -------------------------------------------------------------------------------- 1 | call->send([ 5 | 'to' => '79260000111' 6 | ]); 7 | 8 | echo "Call Send Request Id: " . $response->request_id; 9 | 10 | $response = $client->call->receive([ 11 | 'to' => '79260000111', 12 | 'toll_free' => 'true', 13 | 'tag' => 'aaeb96d6-cb0e-46f2-8d09-2cd5c9ea4211', 14 | ]); 15 | 16 | echo "Call Receive Request Id: " . $response->request_id; 17 | 18 | 19 | $response = $client->call->status([ 20 | 'id' => '1fd4ac4d-6e4f-4e32-b6e4-8087d3466f55' 21 | ]); 22 | 23 | echo "Call Status: \n"; 24 | print_r($response); 25 | -------------------------------------------------------------------------------- /src/Utils/Version.php: -------------------------------------------------------------------------------- 1 | 'v1', 11 | 'v4.0.0' => 'v4.0.0', 12 | ]; 13 | 14 | public static function getVersion($version) 15 | { 16 | if (Helpers::isNullOrEmpty($version)) { 17 | return self::VERSIONS['v1']; 18 | } 19 | $version = strtolower($version); 20 | 21 | if (!array_key_exists($version, self::VERSIONS)) { 22 | throw new Exception('Invalid Version'); 23 | } 24 | 25 | return self::VERSIONS[$version]; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Utils/Validator.php: -------------------------------------------------------------------------------- 1 | mapFieldsRules($schema); 14 | 15 | if ($validator->validate()) { 16 | return null; 17 | } else { 18 | $validationException = new RestException('Validation Error', 0); 19 | $validationException->setParams($validator->errors()); 20 | throw $validationException; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tests/GeneralTest.php: -------------------------------------------------------------------------------- 1 | utility = new Utility(); 13 | } 14 | 15 | public function testCanFetchLookup() 16 | { 17 | $response = $this->utility->getInstance()->whois->lookup(['to' => '79260000000']); 18 | $this->assertObjectHasAttribute('operator', $response); 19 | $this->assertObjectHasAttribute('region', $response); 20 | } 21 | 22 | public function testCanFetchStatus() 23 | { 24 | $response = $this->utility->getInstance()->status(); 25 | $this->assertObjectHasAttribute('status', $response); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | 3 | name: CI 4 | 5 | # Controls when the action will run. 6 | on: 7 | # Allows you to run this workflow manually from the Actions tab 8 | workflow_dispatch: 9 | 10 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 11 | jobs: 12 | # This workflow contains a single job called "build" 13 | build: 14 | # The type of runner that the job will run on 15 | runs-on: ubuntu-latest 16 | 17 | # Steps represent a sequence of tasks that will be executed as part of the job 18 | steps: 19 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 20 | - uses: actions/checkout@v2 21 | 22 | - name: PHP_CodeSniffer Check with Annotations 23 | uses: chekalsky/phpcs-action@v1.2.0 24 | 25 | -------------------------------------------------------------------------------- /src/Utils/Url.php: -------------------------------------------------------------------------------- 1 | 'test', 13 | 'pass' => 'test' 14 | ]); 15 | 16 | return $client; 17 | } 18 | 19 | public function getRandomPhone($min = 70000000111, $max = 70009999999) 20 | { 21 | $phoneNum = $this->getRandomNumber($min, $max); 22 | return $phoneNum; 23 | } 24 | 25 | public function getRandomNumber($min, $max) 26 | { 27 | return strval(rand($min, $max)); 28 | } 29 | 30 | public function getInstanceWithEnv() 31 | { 32 | putenv('GREENSMS_USER=test'); 33 | putenv('GREENSMS_PASS=test'); 34 | $envClient = new GreenSMS(); 35 | 36 | // Unsetting the env variable after use 37 | putenv('GREENSMS_USER'); 38 | putenv('GREENSMS_PASS'); 39 | 40 | return $envClient; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "greensms/greensms", 3 | "description": "GREENSMS API: SMS, WhatsApp, Viber, VK, Voice, Call, HLR", 4 | "type": "library", 5 | "keywords": [ 6 | "greensms", 7 | "sms", 8 | "api", 9 | "rest", 10 | "viber" 11 | ], 12 | "homepage": "https://github.com/greensms-ru/greensms-php", 13 | "license": "MIT", 14 | "authors": [ 15 | { 16 | "name": "Team GreenSMS", 17 | "email": "support@greensms.io" 18 | } 19 | ], 20 | "require": { 21 | "php": ">=7.3", 22 | "vlucas/valitron": "^1.4", 23 | "ext-curl": "*", 24 | "ext-json": "*" 25 | }, 26 | "require-dev": { 27 | "phpunit/phpunit": "^9", 28 | "friendsofphp/php-cs-fixer": "^2.16" 29 | }, 30 | "autoload": { 31 | "psr-4": { 32 | "GreenSMS\\": "src/" 33 | } 34 | }, 35 | "autoload-dev": { 36 | "psr-4": { 37 | "GreenSMS\\Tests\\": "tests/" 38 | } 39 | }, 40 | "scripts": { 41 | "test": "./vendor/bin/phpunit tests --bootstrap vendor/autoload.php" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /tests/EnvTest.php: -------------------------------------------------------------------------------- 1 | utility = new Utility(); 14 | } 15 | 16 | public function testCanFetchLookup() 17 | { 18 | $client = $this->utility->getInstanceWithEnv(); 19 | $response = $client->account->balance(); 20 | $this->assertObjectHasAttribute('balance', $response); 21 | } 22 | 23 | public function testBaseUrl() 24 | { 25 | $subDomain = (string) bin2hex(random_bytes(10)); 26 | putenv('GREENSMS_BASE_URL=https://'. $subDomain .'.greensms.io:'); 27 | $client = $this->utility->getInstance(); 28 | $response = $client->account->balance(); 29 | putenv('GREENSMS_BASE_URL'); 30 | 31 | $this->assertInstanceOf(RestException::class, $response); 32 | $this->assertStringContainsString($subDomain, $response->getMessage()); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Api/Module.php: -------------------------------------------------------------------------------- 1 | restClient = $restClient; 18 | $this->moduleSchema = $moduleSchema; 19 | $this->params = $options; 20 | $this->uri = $uri; 21 | $this->preSendHandler = $preSendHandler; 22 | } 23 | 24 | public function apiFunction($data = []) 25 | { 26 | if (!is_null($this->preSendHandler)) { 27 | call_user_func_array($this->preSendHandler, [&$data, $this->uri, $this->params['method']]); 28 | } 29 | 30 | if ($this->moduleSchema) { 31 | $validationResult = Validator::validate($this->moduleSchema, $data); 32 | } 33 | 34 | $requestParams = $this->params; 35 | $requestParams['params'] = $data; 36 | $response = $this->restClient->request($requestParams); 37 | return $response; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /examples/account.php: -------------------------------------------------------------------------------- 1 | account->balance(); 5 | echo "Balance : " . $response->balance. "\n"; 6 | 7 | $response = $client->account->token(['token' => 100]); 8 | echo "Auth Token: " . $response->access_token; 9 | 10 | $response = $client->account->tariff(); 11 | echo "Tariff Response: "; 12 | print_r($response); 13 | 14 | $response = $client->account->blacklist->add(['to' => '70000000000', 'module' => 'ALL', 'comment' => 'test']); 15 | echo "Blacklist Add Response: "; 16 | print_r($response); 17 | $response = $client->account->blacklist->get(); 18 | echo "Blacklist Get Response: "; 19 | print_r($response); 20 | $response = $client->account->blacklist->delete(['to' => '70000000000']); 21 | echo "Blacklist Delete Response: "; 22 | print_r($response); 23 | 24 | $response = $client->account->limits->set(['type' => 'IP', 'module' => 'ALL', 'value' => '1.1.1.1,8.8.8.8', 'comment' => 'test']); 25 | echo "Limits Set Response: "; 26 | print_r($response); 27 | $response = $client->account->limits->get(); 28 | echo "Limits Get Response: "; 29 | print_r($response); 30 | $response = $client->account->limits->delete(['type' => 'IP']); 31 | echo "Limits Delete Response: "; 32 | print_r($response); -------------------------------------------------------------------------------- /tests/TokenTest.php: -------------------------------------------------------------------------------- 1 | utility = new Utility(); 16 | } 17 | 18 | public function testCanFetchLookup() 19 | { 20 | $tokenResponse = $this->utility->getInstance()->account->token(['expire' => 10]); 21 | 22 | $client = new GreenSMS([ 23 | 'token' => $tokenResponse->access_token 24 | ]); 25 | $response = $client->account->balance(); 26 | $this->assertObjectHasAttribute('balance', $response); 27 | } 28 | 29 | public function testRaisesExceptionOnNoCredentials() 30 | { 31 | $tokenResponse = $this->utility->getInstance()->account->token([ 32 | 'expire' => 5 33 | ]); 34 | 35 | $invalidTokenClient = new GreenSMS([ 36 | 'token' => $tokenResponse->access_token 37 | ]); 38 | 39 | sleep(6); 40 | 41 | try { 42 | $invalidTokenClient->account->balance(); 43 | $this->fail("Shouldn't allow operations on Expired Auth Token"); 44 | } catch (Exception $e) { 45 | $this->assertEquals('Authorization declined', $e->getMessage()); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Http/RestException.php: -------------------------------------------------------------------------------- 1 | name = 'RestException'; 16 | parent::__construct($errorMessage, $code, $previous); 17 | $errorType = $this->getErrorType($this->code); 18 | $this->errorType = $errorType; 19 | } 20 | 21 | public function setParams($params) 22 | { 23 | $this->params = $params; 24 | } 25 | 26 | public function getParams() 27 | { 28 | return $this->params; 29 | } 30 | 31 | public function __toString() 32 | { 33 | $params = json_encode($this->params); 34 | return __CLASS__ . ": [{$this->code}]: {$this->message}\n{$this->getTraceAsString()}\n\n{$params}"; 35 | } 36 | 37 | public function getErrorType($code) 38 | { 39 | switch ($code) { 40 | case 0: 41 | return 'AUTH_DECLINED'; 42 | 43 | case 1: 44 | return 'MISSING_INPUT_PARAM'; 45 | 46 | case 2: 47 | return 'INVALID_INPUT_PARAM'; 48 | 49 | case 404: 50 | return 'NOT_FOUND'; 51 | 52 | default: 53 | return 'INTERNAL_SERVER_ERROR'; 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /tests/VoiceTest.php: -------------------------------------------------------------------------------- 1 | utility = new Utility(); 16 | } 17 | 18 | public function testCanSendMessage() 19 | { 20 | $phoneNum = $this->utility->getRandomPhone(); 21 | $params = [ 22 | 'to' => $phoneNum, 23 | 'txt' => '1127', 24 | 'language' => 'en' 25 | ]; 26 | 27 | $response = $this->utility->getInstance()->voice->send($params); 28 | $this->assertObjectHasAttribute('request_id', $response); 29 | return $response->request_id; 30 | } 31 | 32 | /** 33 | * @depends testCanSendMessage 34 | */ 35 | public function testCanFetchStatus($requestId) 36 | { 37 | sleep(2); 38 | $response = $this->utility->getInstance()->voice->status(['id' => $requestId, 'extended' => true ]); 39 | $this->assertObjectHasAttribute('status', $response); 40 | } 41 | 42 | public function testRaisesValidationException() 43 | { 44 | try { 45 | $response = $this->utility->getInstance()->voice->send([]); 46 | $this->fail("Shouldn't send Voice without parameters"); 47 | } catch (Exception $e) { 48 | $this->assertObjectHasAttribute('message', $e); 49 | $this->assertEquals('Validation Error', $e->getMessage()); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /tests/ViberTest.php: -------------------------------------------------------------------------------- 1 | utility = new Utility(); 16 | } 17 | 18 | public function testCanSendMessage() 19 | { 20 | $phoneNum = $this->utility->getRandomPhone(); 21 | $params = [ 22 | 'to' => $phoneNum, 23 | 'txt' => 'Text Message Hampshire', 24 | 'from' => 'PHPTest', 25 | 'cascade' => 'sms' 26 | ]; 27 | 28 | $response = $this->utility->getInstance()->viber->send($params); 29 | $this->assertObjectHasAttribute('request_id', $response); 30 | return $response->request_id; 31 | } 32 | 33 | /** 34 | * @depends testCanSendMessage 35 | */ 36 | public function testCanFetchStatus($requestId) 37 | { 38 | sleep(2); 39 | $response = $this->utility->getInstance()->viber->status(['id' => $requestId, 'extended' => true ]); 40 | $this->assertObjectHasAttribute('status', $response); 41 | } 42 | 43 | public function testRaisesValidationException() 44 | { 45 | try { 46 | $response = $this->utility->getInstance()->viber->send([]); 47 | $this->fail("Shouldn't send Viber without parameters"); 48 | } catch (Exception $e) { 49 | $this->assertObjectHasAttribute('message', $e); 50 | $this->assertEquals('Validation Error', $e->getMessage()); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /tests/TelegramTest.php: -------------------------------------------------------------------------------- 1 | utility = new Utility(); 14 | } 15 | 16 | public function testCanSendMessage() 17 | { 18 | $phoneNum = $this->utility->getRandomPhone(); 19 | $params = [ 20 | 'to' => $phoneNum, 21 | 'txt' => '1127', 22 | ]; 23 | 24 | $response = $this->utility->getInstance()->telegram->send($params); 25 | $this->assertObjectHasAttribute('request_id', $response); 26 | return $response->request_id; 27 | } 28 | 29 | /** 30 | * @depends testCanSendMessage 31 | */ 32 | public function testCanFetchStatus($requestId) 33 | { 34 | try { 35 | $this->utility->getInstance()->telegram->status(['id' => $requestId, 'extended' => true ]); 36 | } catch (RestException $e) { 37 | $this->assertObjectHasAttribute('message', $e); 38 | $this->assertEquals('Message not found', $e->getMessage()); 39 | } 40 | } 41 | 42 | public function testRaisesValidationException() 43 | { 44 | try { 45 | $this->utility->getInstance()->telegram->send([]); 46 | $this->fail("Shouldn't send Telegram without parameters"); 47 | } catch (Exception $e) { 48 | $this->assertObjectHasAttribute('message', $e); 49 | $this->assertEquals('Validation Error', $e->getMessage()); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /tests/RestClientTest.php: -------------------------------------------------------------------------------- 1 | request([ 14 | 'url'=>'https://gethttpstatus.com/400', 15 | 'method' => 'get', 16 | ]); 17 | 18 | $this->assertInstanceOf(RestException::class, $response); 19 | $this->assertEquals('Bad Request', $response->getMessage()); 20 | $this->assertEquals(400, $response->getCode()); 21 | } 22 | 23 | public function testFail500() 24 | { 25 | $client = new RestClient([]); 26 | $response = $client->request([ 27 | 'url'=>'https://gethttpstatus.com/500', 28 | 'method' => 'get', 29 | ]); 30 | 31 | $this->assertInstanceOf(RestException::class, $response); 32 | $this->assertEquals('Internal Server Error', $response->getMessage()); 33 | $this->assertEquals(500, $response->getCode()); 34 | } 35 | 36 | public function testNativeRequestTimeout () 37 | { 38 | $client = new RestClient([]); 39 | $response = $client->request([ 40 | 'url'=>'https://some.some', 41 | 'method' => 'get', 42 | 'CURLOPT_TIMEOUT_MS' => 2, 43 | ]); 44 | 45 | $this->assertInstanceOf(RestException::class, $response); 46 | $this->assertMatchesRegularExpression( 47 | '/Resolving timed out after (\d){1,3} milliseconds/', 48 | $response->getMessage() 49 | ); 50 | $this->assertEquals(0, $response->getCode()); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /tests/WhatsappTest.php: -------------------------------------------------------------------------------- 1 | utility = new Utility(); 14 | } 15 | 16 | public function testCanSendMessage() 17 | { 18 | $phoneNum = $this->utility->getRandomPhone(); 19 | $params = [ 20 | 'to' => $phoneNum, 21 | 'txt' => '1234 is your verification code.', 22 | 'from' => 'GREENSMS', 23 | 'tag' => 'test-sdk-node' 24 | ]; 25 | 26 | $response = $this->utility->getInstance()->whatsapp->send($params); 27 | $this->assertObjectHasAttribute('request_id', $response); 28 | return $response->request_id; 29 | } 30 | 31 | /** 32 | * @depends testCanSendMessage 33 | */ 34 | /* 35 | public function testCanFetchStatus($requestId) 36 | { 37 | sleep(2); 38 | $response = $this->utility->getInstance()->whatsapp->status(['id' => $requestId, 'extended' => true ]); 39 | $this->assertObjectHasAttribute('status', $response); 40 | } 41 | */ 42 | public function testCanGetChannelList() 43 | { 44 | $response = $this->utility->getInstance()->whatsapp->channel(); 45 | $this->assertObjectHasAttribute('channels', $response); 46 | $this->assertIsArray($response->channels); 47 | } 48 | 49 | public function testRaisesValidationException() 50 | { 51 | try { 52 | $response = $this->utility->getInstance()->whatsapp->send([]); 53 | $this->fail("Shouldn't send Whatsapp without parameters"); 54 | } catch (Exception $e) { 55 | $this->assertObjectHasAttribute('message', $e); 56 | $this->assertEquals('Validation Error', $e->getMessage()); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /.github/workflows/php.yml: -------------------------------------------------------------------------------- 1 | name: PHP Composer 2 | 3 | on: 4 | push: 5 | branches: [main, build-matrix] 6 | pull_request: 7 | branches: [main] 8 | release: 9 | workflow_dispatch: 10 | 11 | jobs: 12 | build: 13 | runs-on: ${{ matrix.operating-system }} 14 | strategy: 15 | matrix: 16 | #@wontfix 'windows-latest', 'macos-latest' 17 | operating-system: ["ubuntu-latest"] 18 | #@wontfix v.5.3 conflicts with php-cs-fixer 19 | #@todo fix v.8 conflicts with phpunit 20 | php-versions: ["7.3", "7.4", "8.2", "8.3"] 21 | #phpunit-versions: ['latest'] 22 | 23 | steps: 24 | - uses: actions/checkout@v2 25 | 26 | - name: Setup PHP 27 | uses: shivammathur/setup-php@v2 28 | with: 29 | php-version: ${{ matrix.php-versions }} 30 | extensions: mbstring, intl, curl 31 | ini-values: post_max_size=256M, max_execution_time=180 32 | #coverage: xdebug 33 | #tools: php-cs-fixer, phpunit:${{ matrix.phpunit-versions }} 34 | 35 | - name: Validate composer.json and composer.lock 36 | run: composer validate 37 | 38 | # - name: Cache Composer packages 39 | # id: composer-cache 40 | # uses: actions/cache@v2 41 | # with: 42 | # path: vendor 43 | # key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }} 44 | # restore-keys: | 45 | # ${{ runner.os }}-php- 46 | 47 | - name: Install dependencies 48 | if: steps.composer-cache.outputs.cache-hit != 'true' 49 | run: composer install --prefer-dist --no-progress --no-suggest 50 | 51 | # Add a test script to composer.json, for instance: "test": "vendor/bin/phpunit" 52 | # Docs: https://getcomposer.org/doc/articles/scripts.md 53 | - name: Composer Autoload 54 | run: composer dump-autoload 55 | 56 | - name: Run test suite 57 | if: ${{ matrix.php-versions == '8.2' }} 58 | run: composer run-script test 59 | -------------------------------------------------------------------------------- /tests/VkTest.php: -------------------------------------------------------------------------------- 1 | utility = new Utility(); 16 | } 17 | 18 | public function testCanSendMessage() 19 | { 20 | $phoneNum = $this->utility->getRandomPhone(); 21 | $params = [ 22 | 'to' => $phoneNum, 23 | 'txt' => '1127', 24 | 'from' => 'GreenSMS', 25 | 'cascade' => 'voice,viber,sms', 26 | ]; 27 | 28 | $response = $this->utility->getInstance()->vk->send($params); 29 | $this->assertObjectHasAttribute('request_id', $response); 30 | return $response->request_id; 31 | } 32 | 33 | /** 34 | * @depends testCanSendMessage 35 | */ 36 | public function testCanFetchStatus($requestId) 37 | { 38 | sleep(2); 39 | $response = $this->utility->getInstance()->vk->status(['id' => $requestId, 'extended' => true ]); 40 | $this->assertObjectHasAttribute('status', $response); 41 | } 42 | 43 | public function testRaisesValidationException() 44 | { 45 | try { 46 | $response = $this->utility->getInstance()->vk->send([]); 47 | $this->fail("Shouldn't send Vk without parameters"); 48 | } catch (Exception $e) { 49 | $this->assertObjectHasAttribute('message', $e); 50 | $this->assertEquals('Validation Error', $e->getMessage()); 51 | } 52 | } 53 | 54 | public function testCommaSeparatedInStrictValidationFailure() 55 | { 56 | $this->expectException(RestException::class); 57 | 58 | $this->utility->getInstance()->vk->send([ 59 | 'to' => $this->utility->getRandomPhone(), 60 | 'txt' => 'Test Message', 61 | 'from' => 'GreenSMS', 62 | 'cascade' => 'voice,viber,invalid', 63 | ]); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /tests/HlrTest.php: -------------------------------------------------------------------------------- 1 | utility = new Utility(); 16 | } 17 | 18 | public function testCanSendMessage() 19 | { 20 | $phoneNum = $this->utility->getRandomPhone(79150000000, 79150999999); 21 | $params = [ 22 | 'to' => $phoneNum, 23 | ]; 24 | 25 | $response = $this->utility->getInstance()->hlr->send($params); 26 | $this->assertObjectHasAttribute('request_id', $response); 27 | return [ 28 | 'id' => $response->request_id, 29 | 'to' => $phoneNum, 30 | ]; 31 | } 32 | 33 | /** 34 | * @depends testCanSendMessage 35 | */ 36 | public function testCanFetchStatus($params) 37 | { 38 | sleep(2); 39 | $response = $this->utility->getInstance()->hlr->status($params); 40 | $this->assertObjectHasAttribute('status', $response); 41 | } 42 | 43 | public function testRaisesValidationException() 44 | { 45 | try { 46 | $this->utility->getInstance()->hlr->send([]); 47 | $this->fail("Shouldn't send Hlr without parameters"); 48 | } catch (Exception $e) { 49 | $this->assertObjectHasAttribute('message', $e); 50 | $this->assertEquals('Validation Error', $e->getMessage()); 51 | } 52 | } 53 | 54 | public function testHlrSend() 55 | { 56 | $to = $this->utility->getRandomPhone(79150000000, 79150999999); 57 | $response = $this->utility->getInstance()->hlr->send([ 58 | 'to' => $to, 59 | ]); 60 | $this->assertObjectHasAttribute('request_id', $response); 61 | 62 | return [$to, $response->request_id]; 63 | } 64 | 65 | /** 66 | * @depends testHlrSend 67 | */ 68 | public function testHlrStatus($params) 69 | { 70 | $response = $this->utility->getInstance()->hlr->status([ 71 | 'to' => $params[0], 72 | 'id' => $params[1], 73 | ]); 74 | 75 | $this->assertObjectHasAttribute('status', $response); 76 | 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /tests/PreSendHandlerTest.php: -------------------------------------------------------------------------------- 1 | utility = new Utility(); 14 | } 15 | 16 | public function testReplacement() 17 | { 18 | $client = new GreenSMS([ 19 | 'user' => 'test', 20 | 'pass' => 'test', 21 | 'preSendHandler' => function(&$data, $uri, $method) { 22 | $this->assertEquals('someVal', $data['someKey']); 23 | $this->assertEquals('sms/send', $uri); 24 | $this->assertEqualsIgnoringCase('post', $method); 25 | $data['to'] = $this->utility->getRandomPhone(); 26 | $data['txt'] = 'txt'; 27 | } 28 | ]); 29 | 30 | $response = $client->sms->send(['someKey' => 'someVal', 'to'=> 111]); 31 | 32 | $this->assertObjectHasProperty('request_id', $response); 33 | } 34 | 35 | public function testEmptyData() 36 | { 37 | $client = new GreenSMS([ 38 | 'user' => 'test', 39 | 'pass' => 'test', 40 | 'preSendHandler' => function($data, $uri, $method) { 41 | $this->assertEquals('account/balance', $uri); 42 | $this->assertEmpty($data); 43 | $this->assertEqualsIgnoringCase('get', $method); 44 | } 45 | ]); 46 | 47 | $response = $client->account->balance(); 48 | 49 | $this->assertObjectHasProperty('balance', $response); 50 | $this->assertEquals(4, $this->getCount()); 51 | } 52 | 53 | public function testInvocable() 54 | { 55 | $invocable = new class extends TestCase{ 56 | public function __invoke($data, $uri, $method) { 57 | $this->assertEquals('account/balance', $uri); 58 | $this->assertEmpty($data); 59 | $this->assertEqualsIgnoringCase('get', $method); 60 | } 61 | }; 62 | 63 | (new GreenSMS([ 64 | 'user' => 'test', 65 | 'pass' => 'test', 66 | 'preSendHandler' => $invocable, 67 | ]))->account->balance(); 68 | } 69 | 70 | public function testV4() 71 | { 72 | $client = new GreenSMS([ 73 | 'user' => 'test', 74 | 'pass' => 'test', 75 | 'preSendHandler' => function($data, $uri, $method) { 76 | $this->assertEmpty($data); 77 | $this->assertEquals('account/webhook', $uri); 78 | $this->assertEqualsIgnoringCase('get', $method); 79 | } 80 | ]); 81 | 82 | $response = $client->account->webhook->get(); 83 | 84 | $this->assertObjectHasProperty('webhook', $response); 85 | $this->assertEquals(4, $this->getCount()); 86 | } 87 | 88 | public function testNotCallable() 89 | { 90 | $this->expectException(BadFunctionCallException::class); 91 | 92 | new GreenSMS([ 93 | 'user' => 'test', 94 | 'pass' => 'test', 95 | 'preSendHandler' => '', 96 | ]); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /tests/CallTest.php: -------------------------------------------------------------------------------- 1 | utility = new Utility(); 15 | } 16 | 17 | public function testCanReceive() 18 | { 19 | $phoneNum = $this->utility->getRandomPhone(); 20 | $params = [ 21 | 'to' => $phoneNum, 22 | 'toll_free' => 'true' 23 | ]; 24 | 25 | $response = $this->utility->getInstance()->call->receive($params); 26 | $this->assertObjectHasAttribute('request_id', $response); 27 | $this->assertObjectHasAttribute('number', $response); 28 | } 29 | 30 | public function testCanSendMessage() 31 | { 32 | $phoneNum = $this->utility->getRandomPhone(); 33 | $params = [ 34 | 'to' => $phoneNum, 35 | ]; 36 | 37 | $response = $this->utility->getInstance()->call->send($params); 38 | $this->assertObjectHasAttribute('request_id', $response); 39 | $this->assertObjectHasAttribute('code', $response); 40 | $requestId = $response->request_id; 41 | return $requestId; 42 | } 43 | 44 | /** 45 | * @depends testCanSendMessage 46 | * */ 47 | public function testCanFetchStatus($requestId) 48 | { 49 | sleep(2); 50 | $response = $this->utility->getInstance()->call->status(['id' => $requestId, 'extended' => true ]); 51 | $this->assertObjectHasAttribute('status', $response); 52 | } 53 | 54 | public function testRaisesValidationException() 55 | { 56 | try { 57 | $response = $this->utility->getInstance()->call->send([]); 58 | $this->fail("Shouldn't send Call without parameters"); 59 | } catch (Exception $e) { 60 | $this->assertObjectHasAttribute('message', $e); 61 | $this->assertEquals('Validation Error', $e->getMessage()); 62 | } 63 | } 64 | 65 | /** @dataProvider receiveParamsDataProvider*/ 66 | public function testReceiveParams($params) 67 | { 68 | $this->expectException(RestException::class); 69 | $this->utility->getInstance()->call->receive($params); 70 | } 71 | 72 | public static function receiveParamsDataProvider(): iterable 73 | { 74 | return [ 75 | 'minTo' => [[ 76 | 'to' => str_repeat('1', 10), 77 | ]], 78 | 'maxTo' => [[ 79 | 'to' => str_repeat('1', 15), 80 | ]], 81 | 'maxTag' => [[ 82 | 'to' => '01234567890', 83 | 'tag' => str_repeat('1', 37), 84 | ]], 85 | ]; 86 | } 87 | 88 | /** @dataProvider sendParamsDataProvider*/ 89 | public function testSendParams($params) 90 | { 91 | $this->expectException(RestException::class); 92 | $this->utility->getInstance()->call->send($params); 93 | } 94 | 95 | public static function sendParamsDataProvider(): iterable 96 | { 97 | return [ 98 | 'minTo' => [[ 99 | 'to' => str_repeat('1', 10), 100 | ]], 101 | 'maxTo' => [[ 102 | 'to' => str_repeat('1', 15), 103 | ]], 104 | 'maxTag' => [[ 105 | 'to' => '01234567890', 106 | 'tag' => str_repeat('s', 37), 107 | ]], 108 | ]; 109 | } 110 | 111 | /** @dataProvider statusParamsDataProvider*/ 112 | public function testStatusParams($params) 113 | { 114 | $this->expectException(RestException::class); 115 | $this->utility->getInstance()->call->status($params); 116 | } 117 | 118 | public static function statusParamsDataProvider(): iterable 119 | { 120 | return [ 121 | 'minId' => [[ 122 | 'id' => str_repeat('s', 35), 123 | ]], 124 | 'maxId' => [[ 125 | 'id' => str_repeat('s', 37), 126 | ]], 127 | ]; 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # greensms-php 2 | 3 | ![GitHub release (latest by date)](https://img.shields.io/github/v/release/greensms-ru/greensms-php) 4 | ![Packagist PHP Version Support](https://img.shields.io/packagist/php-v/greensms/greensms) 5 | ![Packagist Version](https://img.shields.io/packagist/v/greensms/greensms) 6 | [![PHP Composer](https://github.com/greensms-ru/greensms-php/actions/workflows/php.yml/badge.svg?event=release)](https://github.com/greensms-ru/greensms-php/actions/workflows/php.yml) 7 | 8 | ## Documentation 9 | 10 | The documentation for the GREENSMS API can be found [here][apidocs]. 11 | 12 | ## Installation 13 | 14 | ```bash 15 | composer require greensms/greensms 16 | ``` 17 | 18 | ## Sample Usage 19 | 20 | Check out these [code examples](examples) to get up and running quickly. 21 | 22 | ```php 23 | 24 | use GreenSMS\GreenSMS; 25 | 26 | # Register at my.greeensms.ru first 27 | $client = new GreenSMS([ 28 | 'user' => 'test', 29 | 'pass' => 'test' 30 | ]); 31 | 32 | $response = $client->sms->send([ 33 | 'to' => '79260000121', 34 | 'txt' => 'Here is your message for delivery' 35 | ]); 36 | 37 | 38 | echo "Sms Request Id: " . $response->request_id; 39 | 40 | ``` 41 | 42 | ### Environment Variables 43 | 44 | `greensms-php` supports credential storage in environment variables. If no credentials are provided following env vars will be used: `GREENSMS_USER`/`GREENSMS_PASS` OR `GREENSMS_TOKEN`. 45 | 46 | ### Token Auth 47 | 48 | ```php 49 | 50 | use GreenSMS\GreenSMS; 51 | 52 | $tokenClient = new GreenSMS([ 53 | 'token' => 'yourtoken' 54 | ]); 55 | 56 | $response = $tokenClient->account->balance(); 57 | echo "Balance : " . $response->balance. "\n"; 58 | 59 | 60 | ``` 61 | 62 | ## Compatibility 63 | 64 | `greensms-php` is compatible with PHP 7.3+ onwards until the latest PHP Version 65 | 66 | ## Methods 67 | 68 | - You can either use username/password combination or auth token to create an object with constructor 69 | - Each API Function is available as `MODULE.FUNCTION()` 70 | - Parameters for each API can be referred from [here][apidocs] 71 | - Response keys by default are available in `snake_case`. If you want to use `camelCase`, then pass `'camelCaseResponse'= > true`, in the constructor 72 | 73 | ## Handling Exceptions 74 | 75 | - Exceptions for all APIs are thrown with RestException class. It extends the default PHP Exception class. 76 | - Each error, will have a message and code similar to PHP Exceptions. 77 | - In case of _Validation Error_, additional params are available to show field-wise rule failures. Can be accessed by `$e->getParams()` method on the error object 78 | 79 | ## Getting help 80 | 81 | If you need help installing or using the library, please contact us: [support@greensms.io](mailto:support@greensms.io). 82 | 83 | If you've instead found a bug in the library or would like new features added, go ahead and open issues or pull requests against this repo! 84 | 85 | ## Contributing 86 | 87 | Bug fixes, docs, and library improvements are always welcome. Please refer to our [Contributing Guide](CONTRIBUTING.md) for detailed information on how you can contribute. 88 | If you're not familiar with the GitHub pull request/contribution process, [this is a nice tutorial](https://gun.io/blog/how-to-github-fork-branch-and-pull-request/). 89 | 90 | ### Getting Started 91 | 92 | If you want to familiarize yourself with the project, you can start by [forking the repository](https://help.github.com/articles/fork-a-repo/) and [cloning it in your local development environment](https://help.github.com/articles/cloning-a-repository/). The project requires [Node.js](https://nodejs.org) to be installed on your machine. 93 | 94 | After cloning the repository, install the dependencies by running the following command in the directory of your cloned repository: 95 | 96 | ```bash 97 | composer require 98 | ``` 99 | 100 | GreenSMS has all the unit tests defined under **tests** folder with `*Test.php` extension. It uses PHPUnit, which is added as a dev dependency. You can run all the tests using the following command. 101 | 102 | ```bash 103 | ./vendor/bin/phpunit tests 104 | ``` 105 | 106 | [apidocs]: https://api.greensms.io/ 107 | -------------------------------------------------------------------------------- /tests/SmsTest.php: -------------------------------------------------------------------------------- 1 | utility = new Utility(); 16 | } 17 | 18 | public function testCanSendMessage() 19 | { 20 | $phoneNum = $this->utility->getRandomPhone(); 21 | $params = [ 22 | 'to' => $phoneNum, 23 | 'txt' => 'Text Message Hampshire', 24 | 'from' => 'PHPTest', 25 | 'tag' => 'PHPTest', 26 | 'hidden' => 'Hampshire' 27 | ]; 28 | 29 | $response = $this->utility->getInstance()->sms->send($params); 30 | $this->assertObjectHasAttribute('request_id', $response); 31 | return $response->request_id; 32 | } 33 | 34 | /** 35 | * @depends testCanSendMessage 36 | */ 37 | public function testCanFetchStatus($requestId) 38 | { 39 | sleep(2); 40 | $response = $this->utility->getInstance()->sms->status(['id' => $requestId, 'extended' => true ]); 41 | $this->assertObjectHasAttribute('status', $response); 42 | } 43 | 44 | public function testRaisesValidationException() 45 | { 46 | try { 47 | $response = $this->utility->getInstance()->sms->send([]); 48 | $this->fail("Shouldn't send SMS without parameters"); 49 | } catch (Exception $e) { 50 | $this->assertObjectHasAttribute('message', $e); 51 | $this->assertEquals('Validation Error', $e->getMessage()); 52 | } 53 | } 54 | 55 | public function testMinimalTo() 56 | { 57 | $this->expectException(\GreenSMS\Http\RestException::class); 58 | $this->utility->getInstance()->sms->send([ 59 | 'to' => '0123456789', 60 | 'txt' => 'message', 61 | ]); 62 | } 63 | 64 | public function testMaximalTo() 65 | { 66 | $this->expectException(\GreenSMS\Http\RestException::class); 67 | $this->utility->getInstance()->sms->send([ 68 | 'to' => '012345678912345', 69 | 'txt' => 'message', 70 | ]); 71 | } 72 | 73 | public function testMinimalTxt() 74 | { 75 | $this->expectException(\GreenSMS\Http\RestException::class); 76 | $this->utility->getInstance()->sms->send([ 77 | 'to' => '01234567891', 78 | 'txt' => str_repeat('s',0), 79 | ]); 80 | } 81 | 82 | public function testMaximalTxt() 83 | { 84 | $this->expectException(\GreenSMS\Http\RestException::class); 85 | $this->utility->getInstance()->sms->send([ 86 | 'to' => '01234567891', 87 | 'txt' => str_repeat('s',919), 88 | ]); 89 | } 90 | 91 | public function testMaximalFrom() 92 | { 93 | $this->expectException(\GreenSMS\Http\RestException::class); 94 | $this->utility->getInstance()->sms->send([ 95 | 'to' => '01234567891', 96 | 'txt' => 's', 97 | 'from' => str_repeat('s',12), 98 | ]); 99 | } 100 | 101 | public function testMaximalTag() 102 | { 103 | $this->expectException(\GreenSMS\Http\RestException::class); 104 | $this->utility->getInstance()->sms->send([ 105 | 'to' => '01234567891', 106 | 'txt' => 's', 107 | 'tag' => str_repeat('s',37), 108 | ]); 109 | } 110 | 111 | public function testMaximalHidden() 112 | { 113 | $this->expectException(\GreenSMS\Http\RestException::class); 114 | $this->utility->getInstance()->sms->send([ 115 | 'to' => '01234567891', 116 | 'txt' => 's', 117 | 'hidden' => str_repeat('s',919), 118 | ]); 119 | } 120 | 121 | public function testMinimalId() 122 | { 123 | $this->expectException(\GreenSMS\Http\RestException::class); 124 | $this->utility->getInstance()->sms->status([ 125 | 'id' => str_repeat('i', 35), 126 | ]); 127 | } 128 | 129 | public function testMaximalId() 130 | { 131 | $this->expectException(\GreenSMS\Http\RestException::class); 132 | $this->utility->getInstance()->sms->status([ 133 | 'id' => str_repeat('i', 37), 134 | ]); 135 | } 136 | 137 | public function testSendAndFetchWithDifferentArgSeparator() 138 | { 139 | ini_set('arg_separator.output', ';'); 140 | $this->testCanFetchStatus($this->testCanSendMessage()); 141 | ini_set('arg_separator.output', '&'); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/Http/RestClient.php: -------------------------------------------------------------------------------- 1 | token = $options['token']; 27 | } 28 | 29 | if (array_key_exists('defaultData', $options)) { 30 | $this->defaultData = $options['defaultData']; 31 | } 32 | 33 | if (array_key_exists('defaultParams', $options)) { 34 | $this->defaultParams = $options['defaultParams']; 35 | } 36 | 37 | if (array_key_exists('useCamelCase', $options)) { 38 | $this->useCamelCase = $options['useCamelCase']; 39 | } 40 | } 41 | 42 | public function request($options) 43 | { 44 | if (is_null($options)) { 45 | $options = []; 46 | } 47 | 48 | if (!array_key_exists('method', $options)) { 49 | throw new Exception('Http Method is required!'); 50 | } 51 | 52 | if (!array_key_exists('url', $options)) { 53 | throw new Exception('URL is required!'); 54 | } 55 | 56 | $this->ch = curl_init(); 57 | 58 | $headers = [ 59 | 'Content-Type' => 'application/json', 60 | 'User-Agent' => Constants::SDK_NAME. " ".Constants::SDK_VERSION, 61 | ]; 62 | 63 | if ($this->token) { 64 | $headers['Authorization'] = 'Bearer '. $this->token; 65 | } 66 | 67 | if (array_key_exists('headers', $options)) { 68 | $headers = array_merge($headers, $options['headers']); 69 | } 70 | 71 | if (!empty($headers)) { 72 | $headers_arr = []; 73 | foreach ($headers as $k => $v) { 74 | if (is_array($v)) { 75 | foreach ($v as $val) { 76 | $headers_arr[] = "$k: $val"; 77 | } 78 | } else { 79 | $headers_arr[] = "$k: $v"; 80 | } 81 | } 82 | curl_setopt($this->ch, CURLOPT_HTTPHEADER, $headers_arr); 83 | } 84 | 85 | 86 | $params = []; 87 | if (!empty($this->defaultParams)) { 88 | $params = $this->defaultParams; 89 | } 90 | if (array_key_exists('params', $options)) { 91 | $params = array_merge($params, $options['params']); 92 | } 93 | 94 | $url = $options['url']; 95 | 96 | if (strtolower($options['method']) === 'post') { 97 | curl_setopt($this->ch, CURLOPT_POST, 1); 98 | curl_setopt($this->ch, CURLOPT_POSTFIELDS, json_encode($params)); 99 | } elseif (strtolower($options['method']) === 'delete') { 100 | curl_setopt($this->ch, CURLOPT_CUSTOMREQUEST, "DELETE"); 101 | curl_setopt($this->ch, CURLOPT_POSTFIELDS, json_encode($params)); 102 | } else { 103 | curl_setopt($this->ch, CURLOPT_POST, 0); 104 | $params_str = http_build_query($params, '', '&'); 105 | if (strlen($params_str) > 0) { 106 | $url .= (parse_url($url, PHP_URL_QUERY) ? '&' : '?') . $params_str; 107 | } 108 | } 109 | 110 | curl_setopt($this->ch, CURLOPT_URL, $url); 111 | curl_setopt($this->ch, CURLOPT_RETURNTRANSFER, true); 112 | curl_setopt($this->ch, CURLOPT_HEADER, 0); 113 | curl_setopt($this->ch, CURLOPT_SSL_VERIFYHOST, 0); 114 | curl_setopt($this->ch, CURLOPT_SSL_VERIFYPEER, 0); 115 | if (isset($options['CURLOPT_TIMEOUT_MS'])) { 116 | curl_setopt($this->ch, CURLOPT_TIMEOUT_MS, $options['CURLOPT_TIMEOUT_MS']); 117 | } 118 | 119 | $apiResult = curl_exec($this->ch); 120 | $response = json_decode($apiResult, true); 121 | 122 | if (curl_errno($this->ch)) { 123 | $error = curl_error($this->ch); 124 | $response = new RestException($error, curl_getinfo($this->ch)['http_code']); 125 | } elseif (!is_array($response) && curl_getinfo($this->ch)['http_code'] >= 400 ) { 126 | $response = new RestException($apiResult, curl_getinfo($this->ch)['http_code']); 127 | } 128 | 129 | curl_close($this->ch); 130 | 131 | if (is_array($response) && array_key_exists('error', $response)) { 132 | throw new RestException($response['error'], $response['code']); 133 | } 134 | 135 | if ($this->useCamelCase) { 136 | $response = Helpers::camelizeKeys($response); 137 | } 138 | 139 | return (object)$response; 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/GreenSMS.php: -------------------------------------------------------------------------------- 1 | token = $options['token']; 37 | } 38 | 39 | 40 | if (array_key_exists('user', $options)) { 41 | $this->user = $options['user']; 42 | } 43 | 44 | if (array_key_exists('pass', $options)) { 45 | $this->pass = $options['pass']; 46 | } 47 | 48 | if (array_key_exists('useTokenForRequests', $options)) { 49 | $this->useTokenForRequests = $options['useTokenForRequests']; 50 | } 51 | 52 | if (array_key_exists('camelCaseResponse', $options)) { 53 | $this->camelCaseResponse = $options['camelCaseResponse']; 54 | } 55 | 56 | if (array_key_exists('version', $options)) { 57 | $this->version = $options['version']; 58 | } 59 | 60 | if (array_key_exists('baseUrl', $options)) { 61 | $this->baseUrl = $options['baseUrl']; 62 | } 63 | 64 | if (!$this->token) { 65 | $this->token = getenv('GREENSMS_TOKEN'); 66 | } 67 | 68 | 69 | if (!$this->token && !$this->user) { 70 | $this->user = getenv('GREENSMS_USER'); 71 | } 72 | 73 | if (!$this->token && !$this->pass) { 74 | $this->pass = getenv('GREENSMS_PASS'); 75 | } 76 | 77 | if (!$this->token && (!$this->user || !$this->pass)) { 78 | throw new Exception('Either User/Pass or Auth Token is required!'); 79 | } 80 | 81 | $sharedOptions = [ 82 | 'useTokenForRequests' => $this->useTokenForRequests, 83 | 'baseUrl' => $this->baseUrl ?? Url::baseUrl(), 84 | 'restClient' => $this->getHttpClient([ 85 | 'useCamelCase' => $this->camelCaseResponse 86 | ]), 87 | 'version' => Version::getVersion($this->version), 88 | 'preSendHandler' => $this->getPreSendHandler($options), 89 | ]; 90 | 91 | $this->addModules($sharedOptions); 92 | } 93 | 94 | public function addModules($sharedOptions) 95 | { 96 | $moduleLoader = new ModuleLoader(); 97 | $modules = $moduleLoader->registerModules($sharedOptions); 98 | foreach ($modules as $moduleName => $moduleTree) { 99 | $this->{$moduleName} = $moduleTree; 100 | } 101 | 102 | ValitronValidator::addRule('ipsCommaSeparator', function($field, $value, array $params, array $fields) { 103 | if ($fields['type'] == 'IP') { 104 | foreach (explode(',', $value) as $val) { 105 | if (!filter_var($val, FILTER_VALIDATE_IP)) { 106 | return false; 107 | } 108 | } 109 | } 110 | 111 | return true; 112 | }, 'IP incorrect'); 113 | 114 | Validator::addRule('commaSeparatedInStrict', function ($field, $value, array $params) { 115 | if (!is_string($value)) { 116 | return false; 117 | } 118 | 119 | $allowedValues = $params[0] ?? []; 120 | $items = explode(',', $value); 121 | 122 | foreach ($items as $item) { 123 | if (!in_array($item, $allowedValues, true)) { 124 | return false; 125 | } 126 | } 127 | 128 | return true; 129 | }, 'must contain values from the list with comma: %s'); 130 | } 131 | 132 | public function getHttpClient($args) 133 | { 134 | $defaultParams = []; 135 | 136 | if (!$this->token && $this->user) { 137 | $defaultParams['user'] = $this->user; 138 | $defaultParams['pass'] = $this->pass; 139 | } 140 | 141 | $httpParams = [ 142 | 'defaultParams' => $defaultParams, 143 | 'defaultData' => [], 144 | 'token' => $this->token 145 | ]; 146 | 147 | $httpParams = array_merge($httpParams, $args); 148 | 149 | $restClient = new RestClient($httpParams); 150 | return $restClient; 151 | } 152 | 153 | private function getPreSendHandler($options): ?callable 154 | { 155 | if (array_key_exists('preSendHandler', $options)) { 156 | if (is_callable($options['preSendHandler'])) { 157 | return $options['preSendHandler']; 158 | } else { 159 | throw new BadFunctionCallException('Key `preSendHandler` must be callable'); 160 | } 161 | } 162 | 163 | return null; 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /tests/AccountTest.php: -------------------------------------------------------------------------------- 1 | utility = new Utility(); 15 | } 16 | 17 | public function testCanFetchBalance(): void 18 | { 19 | $response = $this->utility->getInstance()->account->balance(); 20 | $this->assertObjectHasAttribute('balance', $response); 21 | } 22 | 23 | public function testCanFetchToken(): void 24 | { 25 | $response = $this->utility->getInstance()->account->token(['expire' => 10]); 26 | $this->assertObjectHasAttribute('access_token', $response); 27 | } 28 | 29 | public function testCanFetchTariff() 30 | { 31 | $response = $this->utility->getInstance()->account->tariff(); 32 | $this->assertObjectHasAttribute('tariff', $response); 33 | $this->assertGreaterThan(0, count($response->tariff)); 34 | } 35 | 36 | public function testRaisesExceptionOnNoCredentials() 37 | { 38 | try { 39 | $client = new GreenSMS(); 40 | $this->fail("Shouldn't create client without Credentials"); 41 | } catch (Exception $e) { 42 | $this->assertObjectHasAttribute('message', $e); 43 | } 44 | } 45 | 46 | public function testRaisesExceptionOnInvalidCredentials() 47 | { 48 | $client = new GreenSMS([ 49 | 'user' => 'randomusername', 50 | 'pass' => 'pass' 51 | ]); 52 | $this->expectException(RestException::class); 53 | $this->expectExceptionMessage('Authorization declined'); 54 | $this->expectExceptionCode(0); 55 | 56 | $client->account->balance(); 57 | } 58 | 59 | public function testRaisesExceptionOnInsufficientFunds() 60 | { 61 | $client = new GreenSMS([ 62 | 'user' => 'test_block_user', 63 | 'pass' => '183456' 64 | ]); 65 | 66 | $this->expectException(RestException::class); 67 | $this->expectExceptionMessage('Insufficient funds'); 68 | $this->expectExceptionCode(-1); 69 | 70 | $client->sms->send([ 71 | 'to' => $this->utility->getRandomPhone(), 72 | 'txt' => 'Test134' 73 | ]); 74 | } 75 | 76 | public function testBlackList() 77 | { 78 | $response = $this->utility->getInstance()->account->blacklist->delete(['to' => '70000000000']); 79 | $this->assertEquals((object)['success' => 1], $response); 80 | $response = $this->utility->getInstance()->account->blacklist->add(['to' => '70000000000', 'module' => 'ALL', 'comment' => 'test']); 81 | $this->assertEquals((object)['success' => 1], $response); 82 | $this->utility->getInstance()->account->blacklist->get(); 83 | } 84 | 85 | public function testLimits() 86 | { 87 | $response = $this->utility->getInstance()->account->limits->delete(['type' => 'IP']); 88 | $this->assertEquals((object)['success' => 1], $response); 89 | $this->utility->getInstance()->account->limits->get(); 90 | $response = $this->utility->getInstance()->account->limits->set(['type' => 'IP', 'module' => 'ALL', 'value' => '1.1.1.1,8.8.8.8', 'comment' => 'test']); 91 | $this->assertEquals((object)['success' => 1], $response); 92 | $response = $this->utility->getInstance()->account->limits->set(['type' => 'REQ_PER_DAY', 'module' => 'ALL', 'value' => '6000', 'comment' => 'test']); 93 | $this->assertEquals((object)['success' => 1], $response); 94 | } 95 | 96 | public function testWebhook() 97 | { 98 | $response = $this->utility->getInstance()->account->webhook->delete(); 99 | $this->assertEquals((object)['success' => 1], $response); 100 | $this->utility->getInstance()->account->webhook->get(); 101 | $response = $this->utility->getInstance()->account->webhook->set(['url' => 'http://localhost', 'token' => 'test']); 102 | $this->assertEquals((object)['success' => 1], $response); 103 | } 104 | 105 | public function testWhitelist() 106 | { 107 | $response = $this->utility->getInstance()->account->whitelist->delete(['to' => '70000000000']); 108 | $this->assertEquals((object)['success' => 1], $response); 109 | $this->utility->getInstance()->account->whitelist->get(); 110 | $response = $this->utility->getInstance()->account->whitelist->add(['to' => '70000000000', 'module' => 'ALL', 'comment' => 'test']); 111 | $this->assertEquals((object)['success' => 1], $response); 112 | } 113 | 114 | public function testWhitelistUseRules() 115 | { 116 | $this->expectException(RestException::class); 117 | 118 | $this->utility->getInstance()->account->whitelist->add([ 119 | 'to' => '70000000000', 120 | 'module' => 'ALL', 121 | 'comment' => str_repeat('s', 51), 122 | ]); 123 | } 124 | 125 | public function testPasswordResetCode() 126 | { 127 | try { 128 | $response = $this->utility->getInstance()->account->password->resetCode(); 129 | $this->assertObjectHasAttribute('success', $response); 130 | $this->assertTrue($response->success); 131 | } catch (RestException $t) { 132 | $this->assertEquals(3, $t->getCode()); 133 | } 134 | } 135 | 136 | public function testPasswordResetValidation() 137 | { 138 | $this->expectException(RestException::class); 139 | $this->expectExceptionMessage('Validation Error'); 140 | 141 | $this->utility->getInstance()->account->password->reset(); 142 | } 143 | 144 | public function testPasswordReset() 145 | { 146 | $this->expectException(RestException::class); 147 | $this->expectExceptionMessage('Invalid or expired code'); 148 | $this->expectExceptionCode(2); 149 | 150 | $this->utility->getInstance()->account->password->reset(['code' => '123456']); 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /phpcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | The coding standard for PHP_CodeSniffer itself. 4 | 5 | examples 6 | src 7 | tests 8 | 9 | */src/Standards/*/Tests/*\.(inc|css|js)$ 10 | */tests/Core/*/*\.(inc|css|js)$ 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | error 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 0 87 | 88 | 89 | 0 90 | 91 | 92 | 0 93 | 94 | 95 | 0 96 | 97 | 98 | 99 | 100 | 101 | 0 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | error 135 | 136 | 137 | 138 | 139 | error 140 | 141 | 142 | 143 | 144 | tests/bootstrap\.php 145 | 146 | 147 | 148 | 149 | tests/Core/Tokenizer/StableCommentWhitespaceWinTest\.php 150 | 151 | 152 | 153 | -------------------------------------------------------------------------------- /src/Api/ModuleLoader.php: -------------------------------------------------------------------------------- 1 | moduleMap = new MethodInvoker; 19 | 20 | $currentVersion = $sharedOptions['version']; 21 | $modules = Modules::getModules(); 22 | 23 | foreach ($modules as $moduleName => $moduleInfo) { 24 | if (!property_exists($this->moduleMap, $moduleName)) { 25 | $this->moduleMap->{$moduleName} = new MethodInvoker; 26 | } 27 | 28 | $moduleVersions = $moduleInfo['versions']; 29 | $processStaticOnly = Helpers::keyExistsAndTrue('loadStatic', $filters) && Helpers::keyExistsAndTrue('static', $moduleInfo); 30 | 31 | if ($processStaticOnly) { 32 | continue; 33 | } 34 | 35 | $isStaticModule = Helpers::keyExistsAndTrue('static', $moduleInfo); 36 | 37 | 38 | 39 | foreach ($moduleVersions as $version => $versionFunctions) { 40 | 41 | if ($version === 'v1') { 42 | $this->addModuleMapV1($versionFunctions, $moduleInfo, $version, $isStaticModule, $moduleName, $sharedOptions, $currentVersion); 43 | } elseif ($version === 'v4.0.0') { 44 | $this->addModuleMapV4_0_0($versionFunctions, $moduleInfo, $version, $isStaticModule, $moduleName, $sharedOptions, $currentVersion); 45 | } 46 | 47 | } 48 | } 49 | 50 | return $this->moduleMap; 51 | } 52 | 53 | private function doesSchemaExists($moduleInfo, $version, $functionName) 54 | { 55 | if (!array_key_exists('schema', $moduleInfo)) { 56 | return false; 57 | } elseif (!array_key_exists($version, $moduleInfo['schema'])) { 58 | return false; 59 | } elseif (!array_key_exists($functionName, $moduleInfo['schema'][$version])) { 60 | return false; 61 | } 62 | return true; 63 | } 64 | 65 | private function getFunctionInstance($options) 66 | { 67 | $restClient = $options['sharedOptions']['restClient']; 68 | $moduleSchema = $options['moduleSchema']; 69 | 70 | $requestArgs = [ 71 | 'url' => $options['url'], 72 | 'method' => $options['definition']['method'] 73 | ]; 74 | 75 | $module = new Module($restClient, $moduleSchema, $requestArgs, $options['uri'], $options['sharedOptions']['preSendHandler']); 76 | $functionInstance = array($module, 'apiFunction'); 77 | return $functionInstance; 78 | } 79 | 80 | private function addModuleMapV1($versionFunctions, $moduleInfo, $version, $isStaticModule, $moduleName, $sharedOptions, $currentVersion) 81 | { 82 | if (!property_exists($this->moduleMap->{$moduleName}, $version)) { 83 | $this->moduleMap->{$moduleName}->{$version} = new MethodInvoker; 84 | } 85 | 86 | foreach ($versionFunctions as $functionName => $functionDefinition) { 87 | $moduleSchema = null; 88 | $schemaExists = self::doesSchemaExists($moduleInfo, $version, $functionName); 89 | if ($schemaExists) { 90 | $moduleSchema = $moduleInfo['schema'][$version][$functionName]; 91 | } 92 | 93 | $urlParts = []; 94 | if (!$isStaticModule) { 95 | array_push($urlParts, $moduleName); 96 | } 97 | 98 | array_push($urlParts, $functionName); 99 | $apiUrl = Url::buildUrl($sharedOptions['baseUrl'], $urlParts); 100 | 101 | $functionOptions = [ 102 | 'url' => $apiUrl, 103 | 'definition' => $functionDefinition, 104 | 'sharedOptions' => $sharedOptions, 105 | 'moduleSchema' => $moduleSchema, 106 | 'uri' => implode('/', array_map(function($v) { 107 | return rtrim($v, '/'); 108 | }, $urlParts)), 109 | ]; 110 | 111 | $this->moduleMap->{$moduleName}->{$version}->{$functionName} = $this->getFunctionInstance($functionOptions); 112 | 113 | if ($version === $currentVersion) { 114 | $this->moduleMap->{$moduleName}->{$functionName} = $this->moduleMap->{$moduleName}->{$version}->{$functionName}; 115 | } 116 | 117 | if ($isStaticModule) { 118 | $this->moduleMap->{$functionName} = $this->moduleMap->{$moduleName}->{$version}->{$functionName}; 119 | unset($this->moduleMap->{$moduleName}); 120 | } 121 | } 122 | } 123 | 124 | private function addModuleMapV4_0_0($functions, $moduleInfo, $version, $isStaticModule, $moduleName, $sharedOptions, $currentVersion) 125 | { 126 | 127 | foreach ($functions as $prop => $definitions) { 128 | if (!property_exists($this->moduleMap->{$moduleName}, $prop)) { 129 | $this->moduleMap->{$moduleName}->{$prop} = new MethodInvoker; 130 | } 131 | if (!property_exists($this->moduleMap->{$moduleName}->{$prop}, $version)) { 132 | $this->moduleMap->{$moduleName}->{$prop}->{$version} = new MethodInvoker; 133 | } 134 | 135 | foreach ($definitions as $functionName => $functionDefinition) { 136 | $urlParts = []; 137 | if (!$isStaticModule) { 138 | array_push($urlParts, $moduleName); 139 | } 140 | 141 | array_push($urlParts, $prop); 142 | 143 | if (isset($functionDefinition['segments'])) { 144 | array_push($urlParts, ...$functionDefinition['segments']); 145 | } 146 | 147 | $apiUrl = Url::buildUrl($sharedOptions['baseUrl'], $urlParts); 148 | 149 | $functionOptions = [ 150 | 'url' => $apiUrl, 151 | 'definition' => $functionDefinition, 152 | 'sharedOptions' => $sharedOptions, 153 | 'moduleSchema' => $this->getSchema($moduleInfo['schema'] ?? [], $version, $prop, $functionName), 154 | 'uri' => implode('/', array_map(function($v) { 155 | return rtrim($v, '/'); 156 | }, $urlParts)), 157 | ]; 158 | 159 | 160 | $this->moduleMap->{$moduleName}->{$prop}->{$version}->{$functionName} = $this->getFunctionInstance($functionOptions); 161 | $this->moduleMap->{$moduleName}->{$prop}->{$functionName} = $this->moduleMap->{$moduleName}->{$prop}->{$version}->{$functionName}; 162 | 163 | } 164 | } 165 | } 166 | 167 | private function getSchema($schema, $version, $prop, $functionName): ?array 168 | { 169 | if (!array_key_exists($version, $schema)) { 170 | return null; 171 | } elseif (!array_key_exists($prop, $schema[$version])) { 172 | return null; 173 | } elseif (!array_key_exists($functionName, $schema[$version][$prop])) { 174 | return null; 175 | } 176 | 177 | return $schema[$version][$prop][$functionName]; 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /src/Api/Schema.php: -------------------------------------------------------------------------------- 1 | [ 38 | 'send' => [ 39 | 'to' => self::getToSchema() 40 | ], 41 | 'status' => [ 42 | 'id' => self::getIdSchema(), 43 | 'extended' => [['subset', ['true', 'false']]] 44 | ] 45 | ] 46 | ]; 47 | 48 | return $commonSchema; 49 | } 50 | 51 | public static function getSchema() 52 | { 53 | $commonSchema = self::getCommonSchema(); 54 | $toSchema = self::getToSchema(); 55 | 56 | $schema = [ 57 | 'account' => [ 58 | 'v1' => [ 59 | 'token' => [ 60 | 'expire' => ['integer', ['min', 0]], 61 | ] 62 | ], 63 | 'v4.0.0' => [ 64 | 'blacklist' => [ 65 | 'add' => [ 66 | 'to' => $toSchema, 67 | 'module' => self::getModuleSchema(), 68 | 'comment' => [['lengthMax', 50]], 69 | ], 70 | 'delete' => [ 71 | 'to' => $toSchema, 72 | ], 73 | ], 74 | 'password' => [ 75 | 'reset' => [ 76 | 'code' => ['required'], 77 | ], 78 | ], 79 | 'limits' => [ 80 | 'set' => [ 81 | 'type' => self::getTypeSchema(), 82 | 'value' => ['required',['ipsCommaSeparator']], 83 | 'module' => self::getModuleSchemaWithAccount(), 84 | 'comment' => [['lengthMax', 50]], 85 | ], 86 | 'delete' => [ 87 | 'type' => self::getTypeSchema(), 88 | 'module' => self::getModuleSchemaWithAccount(), 89 | ], 90 | ], 91 | 'webhook' => [ 92 | 'set' => [ 93 | 'url' => ['required',['url'], ['lengthMax', 1024]], 94 | 'token' => [['lengthMax', 256]], 95 | ], 96 | ], 97 | 'whitelist' => [ 98 | 'add' => [ 99 | 'to' => $toSchema, 100 | 'module' => self::getModuleSchema(), 101 | 'comment' => [['lengthMax', 50]], 102 | ], 103 | 'delete' => [ 104 | 'to' => $toSchema, 105 | ], 106 | ], 107 | ], 108 | ], 109 | 'call' => array_merge_recursive($commonSchema, [ 110 | 'v1' => [ 111 | 'send' => [ 112 | 'voice' => [['subset', ['true', 'false']]], 113 | 'tag' => [['lengthMax', 36]], 114 | 'lang' => [['subset', ['ru', 'en']]] 115 | ], 116 | 'receive' => [ 117 | 'to' => self::getToSchema(), 118 | 'toll_free' => [['subset', ['true', 'false']]], 119 | 'tag' => [['lengthMax', 36]], 120 | ] 121 | ] 122 | ]) , 123 | 'hlr' => [ 124 | 'v1' => [ 125 | 'send' => [ 126 | 'to' => self::getToSchema(), 127 | ], 128 | 'status' => [ 129 | 'to' => self::getToSchema(), 130 | 'id' => self::getIdSchema(), 131 | ], 132 | ], 133 | ], 134 | 'general' => [], 135 | 'whois' => [ 136 | 'v1' => [ 137 | 'lookup' => [ 138 | 'to' => $toSchema 139 | ] 140 | ], 141 | ], 142 | 'voice' => array_merge_recursive($commonSchema, [ 143 | 'v1' => [ 144 | 'send' => [ 145 | 'txt' => ['required', ['lengthMin', 1], ['lengthMax', 5], ['regex', '/^[0-9]+$/']], 146 | 'tag' => [['lengthMax', 36]], 147 | 'lang' => [['subset', ['ru', 'en']]] 148 | ] 149 | ] 150 | ]), 151 | 'vk' => array_merge_recursive($commonSchema, [ 152 | 'v1' => [ 153 | 'send' => [ 154 | 'txt' => ['required', ['lengthMin', 1], ['lengthMax', 2048]], 155 | 'from' => ['required',['lengthMax', 11], ['lengthMin', 1]], 156 | 'tag' => [['lengthMax', 36]], 157 | 'cascade' => [['commaSeparatedInStrict', ['viber', 'sms', 'voice']]], 158 | ], 159 | ], 160 | ]), 161 | 'sms' => array_merge_recursive($commonSchema, [ 162 | 'v1' => [ 163 | 'send' => [ 164 | 'txt' => ['required', ['lengthMin', 1], ['lengthMax', 918]], 165 | 'from' => [['lengthMax', 11]], 166 | 'tag' => [['lengthMax', 36]], 167 | 'hidden' => [['lengthMax', 918]] 168 | ] 169 | ] 170 | ]), 171 | 'viber' => array_merge_recursive($commonSchema, [ 172 | 'v1' => [ 173 | 'send' => [ 174 | 'txt' => ['required', ['lengthMin', 1]], 175 | 'from' => [['lengthMin', 1],['lengthMax', 11]], 176 | 'cascade' => [['subset', ['sms', 'voice']]], 177 | ] 178 | ] 179 | ]), 180 | 'whatsapp' => array_merge_recursive($commonSchema, [ 181 | 'v1' => [ 182 | 'send' => [ 183 | 'from' => ['required'], 184 | 'txt' => ['required', ['lengthMin', 1],['lengthMax', 1000]], 185 | 'file' => [['lengthMax', 256]], 186 | 'tag' => [['lengthMax', 36]], 187 | ], 188 | 'channel' => [], 189 | ] 190 | ]), 191 | 'telegram' => array_merge_recursive($commonSchema, [ 192 | 'v1' => [ 193 | 'send' => [ 194 | 'txt' => ['required', ['lengthMin', 4], ['lengthMax', 8], ['regex', '/^[0-9]+$/']], 195 | 'tag' => [['lengthMax', 36]], 196 | ] 197 | ] 198 | ]), 199 | ]; 200 | 201 | return $schema; 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /src/Api/Modules.php: -------------------------------------------------------------------------------- 1 | [ 13 | 'schema' => $schema['account'], 14 | 'versions' => [ 15 | 'v1' => [ 16 | 'balance' => [ 17 | 'args' => null, 18 | 'method' => 'GET' 19 | ], 20 | 'token' => [ 21 | 'args' => ['params'], 22 | 'method' => 'POST' 23 | ], 24 | 'tariff' => [ 25 | 'args' => null, 26 | 'method' => 'GET' 27 | ], 28 | ], 29 | 'v4.0.0' => [ 30 | 'blacklist' => [ 31 | 'get' => [ 32 | 'args' => null, 33 | 'method' => 'GET', 34 | ], 35 | 'add' => [ 36 | 'args' => ['params'], 37 | 'method' => 'POST', 38 | ], 39 | 'delete' => [ 40 | 'args' => ['params'], 41 | 'method' => 'DELETE', 42 | ], 43 | ], 44 | 'password' => [ 45 | 'resetCode' => [ 46 | 'args' => null, 47 | 'method' => 'POST', 48 | 'segments' => ['reset-code'] 49 | ], 50 | 'reset' => [ 51 | 'args' => ['params'], 52 | 'method' => 'POST', 53 | 'segments' => ['reset'] 54 | ], 55 | ], 56 | 'limits' => [ 57 | 'get' => [ 58 | 'args' => null, 59 | 'method' => 'GET', 60 | ], 61 | 'set' => [ 62 | 'args' => null, 63 | 'method' => 'POST', 64 | ], 65 | 'delete' => [ 66 | 'args' => null, 67 | 'method' => 'DELETE', 68 | ], 69 | ], 70 | 'webhook' => [ 71 | 'get' => [ 72 | 'args' => null, 73 | 'method' => 'GET', 74 | ], 75 | 'set' => [ 76 | 'args' => null, 77 | 'method' => 'POST', 78 | ], 79 | 'delete' => [ 80 | 'args' => null, 81 | 'method' => 'DELETE', 82 | ], 83 | ], 84 | 'whitelist' => [ 85 | 'get' => [ 86 | 'args' => null, 87 | 'method' => 'GET', 88 | ], 89 | 'add' => [ 90 | 'args' => null, 91 | 'method' => 'POST', 92 | ], 93 | 'delete' => [ 94 | 'args' => null, 95 | 'method' => 'DELETE', 96 | ], 97 | ], 98 | ], 99 | ] 100 | ], 101 | 'call' => [ 102 | 'schema' => $schema['call'], 103 | 'versions' => [ 104 | 'v1' => [ 105 | 'send'=> [ 106 | 'args'=> ['params'], 107 | 'method'=> 'POST', 108 | ], 109 | 'receive'=> [ 110 | 'args'=> ['params'], 111 | 'method'=> 'POST', 112 | ], 113 | 'status'=> [ 114 | 'args'=> ['params'], 115 | 'method'=> 'GET', 116 | ], 117 | ] 118 | ] 119 | ], 120 | 'whois' => [ 121 | 'schema' => $schema['whois'], 122 | 'versions' => [ 123 | 'v1' => [ 124 | 'lookup' => [ 125 | 'args' => ['params'], 126 | 'method' => 'GET' 127 | ] 128 | ] 129 | ] 130 | ], 131 | 'general' => [ 132 | 'schema' => $schema['general'], 133 | 'static' => true, 134 | 'versions' => [ 135 | 'v1' => [ 136 | 'status' => [ 137 | 'args' => null, 138 | 'method' => 'GET', 139 | ], 140 | ], 141 | ], 142 | ], 143 | 'voice' => [ 144 | 'schema' => $schema['voice'], 145 | 'versions' => [ 146 | 'v1' => [ 147 | 'send' => [ 148 | 'args' => ['params'], 149 | 'method' => 'POST', 150 | ], 151 | 'status' => [ 152 | 'args' => ['params'], 153 | 'method' => 'GET', 154 | ], 155 | ] 156 | ] 157 | ], 158 | 'vk' => [ 159 | 'schema' => $schema['vk'], 160 | 'versions' => [ 161 | 'v1' => [ 162 | 'send' => [ 163 | 'args' => ['params'], 164 | 'method' => 'POST', 165 | ], 166 | 'status' => [ 167 | 'args' => ['params'], 168 | 'method' => 'GET', 169 | ], 170 | ] 171 | ] 172 | ], 173 | 'whatsapp' => [ 174 | 'schema' => $schema['whatsapp'], 175 | 'versions' => [ 176 | 'v1' => [ 177 | 'send' => [ 178 | 'args' => ['params'], 179 | 'method' => 'POST', 180 | ], 181 | 'status' => [ 182 | 'args' => ['params'], 183 | 'method' => 'GET', 184 | ], 185 | 'channel' => [ 186 | 'args' => null, 187 | 'method' => 'GET', 188 | ], 189 | ] 190 | ] 191 | ], 192 | 'hlr' => [ 193 | 'schema' => $schema['hlr'], 194 | 'versions' => [ 195 | 'v1' => [ 196 | 'send' => [ 197 | 'args' => ['params'], 198 | 'method' => 'POST', 199 | ], 200 | 'status' => [ 201 | 'args' => ['params'], 202 | 'method' => 'GET', 203 | ], 204 | ] 205 | ] 206 | ], 207 | 'sms' => [ 208 | 'schema' => $schema['sms'], 209 | 'versions' => [ 210 | 'v1' => [ 211 | 'send' => [ 212 | 'args' => ['params'], 213 | 'method' => 'POST', 214 | ], 215 | 'status' => [ 216 | 'args' => ['params'], 217 | 'method' => 'GET', 218 | ], 219 | ] 220 | ] 221 | ], 222 | 'viber' => [ 223 | 'schema' => $schema['viber'], 224 | 'versions' => [ 225 | 'v1' => [ 226 | 'send' => [ 227 | 'args' => ['params'], 228 | 'method' => 'POST', 229 | ], 230 | 'status' => [ 231 | 'args' => ['params'], 232 | 'method' => 'GET', 233 | ], 234 | ] 235 | ] 236 | ], 237 | 'telegram' => [ 238 | 'schema' => $schema['telegram'], 239 | 'versions' => [ 240 | 'v1' => [ 241 | 'send' => [ 242 | 'args' => ['params'], 243 | 'method' => 'POST', 244 | ], 245 | 'status' => [ 246 | 'args' => ['params'], 247 | 'method' => 'GET', 248 | ], 249 | ] 250 | ] 251 | ], 252 | ]; 253 | } 254 | } 255 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ##### Windows 2 | # Windows thumbnail cache files 3 | Thumbs.db 4 | Thumbs.db:encryptable 5 | ehthumbs.db 6 | ehthumbs_vista.db 7 | 8 | # Dump file 9 | *.stackdump 10 | 11 | # Folder config file 12 | [Dd]esktop.ini 13 | 14 | # Recycle Bin used on file shares 15 | $RECYCLE.BIN/ 16 | 17 | # Windows Installer files 18 | *.cab 19 | *.msi 20 | *.msix 21 | *.msm 22 | *.msp 23 | 24 | # Windows shortcuts 25 | *.lnk 26 | 27 | ##### Linux 28 | *~ 29 | package.xml 30 | *.tgz 31 | 32 | 33 | # temporary files which can be created if a process still has a handle open of a deleted file 34 | .fuse_hidden* 35 | 36 | # KDE directory preferences 37 | .directory 38 | 39 | # Linux trash folder which might appear on any partition or disk 40 | .Trash-* 41 | 42 | # .nfs files are created when an open file is removed but is still being accessed 43 | .nfs* 44 | 45 | ##### MacOS 46 | # General 47 | .DS_Store 48 | .AppleDouble 49 | .LSOverride 50 | 51 | # Icon must end with two \r 52 | Icon 53 | 54 | # Thumbnails 55 | ._* 56 | 57 | # Files that might appear in the root of a volume 58 | .DocumentRevisions-V100 59 | .fseventsd 60 | .Spotlight-V100 61 | .TemporaryItems 62 | .Trashes 63 | .VolumeIcon.icns 64 | .com.apple.timemachine.donotpresent 65 | 66 | # Directories potentially created on remote AFP share 67 | .AppleDB 68 | .AppleDesktop 69 | Network Trash Folder 70 | Temporary Items 71 | .apdisk 72 | 73 | ##### Backup 74 | *.bak 75 | *.gho 76 | *.ori 77 | *.orig 78 | *.tmp 79 | 80 | ##### GPG 81 | secring.* 82 | 83 | ##### Dropbox 84 | # Dropbox settings and caches 85 | .dropbox 86 | .dropbox.attr 87 | .dropbox.cache 88 | 89 | ##### SynopsysVCS 90 | # Waveform formats 91 | *.vcd 92 | *.vpd 93 | *.evcd 94 | *.fsdb 95 | 96 | # Default name of the simulation executable. A different name can be 97 | # specified with this switch (the associated daidir database name is 98 | # also taken from here): -o / 99 | simv 100 | 101 | # Generated for Verilog and VHDL top configs 102 | simv.daidir/ 103 | simv.db.dir/ 104 | 105 | # Infrastructure necessary to co-simulate SystemC models with 106 | # Verilog/VHDL models. An alternate directory may be specified with this 107 | # switch: -Mdir= 108 | csrc/ 109 | 110 | # Log file - the following switch allows to specify the file that will be 111 | # used to write all messages from simulation: -l 112 | *.log 113 | /logs/* 114 | 115 | 116 | # Coverage results (generated with urg) and database location. The 117 | # following switch can also be used: urg -dir .vdb 118 | simv.vdb/ 119 | urgReport/ 120 | coverage 121 | 122 | # DVE and UCLI related files. 123 | DVEfiles/ 124 | ucli.key 125 | 126 | # When the design is elaborated for DirectC, the following file is created 127 | # with declarations for C/C++ functions. 128 | vc_hdrs.h 129 | 130 | ##### SVN 131 | .svn/ 132 | 133 | ##### Mercurial 134 | .hg/ 135 | .hgignore 136 | .hgsigs 137 | .hgsub 138 | .hgsubstate 139 | .hgtags 140 | 141 | ##### Bazaar 142 | .bzr/ 143 | .bzrignore 144 | 145 | ##### CVS 146 | /CVS/* 147 | **/CVS/* 148 | .cvsignore 149 | */.cvsignore 150 | 151 | ##### TortoiseGit 152 | # Project-level settings 153 | /.tgitconfig 154 | 155 | ##### PuTTY 156 | # Private key 157 | *.ppk 158 | 159 | ##### Vim 160 | # Swap 161 | [._]*.s[a-v][a-z] 162 | !*.svg # comment out if you don't need vector files 163 | [._]*.sw[a-p] 164 | [._]s[a-rt-v][a-z] 165 | [._]ss[a-gi-z] 166 | [._]sw[a-p] 167 | 168 | # Session 169 | Session.vim 170 | Sessionx.vim 171 | 172 | # Temporary 173 | .netrwhist 174 | *~ 175 | # Auto-generated tag files 176 | tags 177 | # Persistent undo 178 | [._]*.un~ 179 | 180 | ##### Emacs 181 | # -*- mode: gitignore; -*- 182 | *~ 183 | \#*\# 184 | /.emacs.desktop 185 | /.emacs.desktop.lock 186 | *.elc 187 | auto-save-list 188 | tramp 189 | .\#* 190 | 191 | # Org-mode 192 | .org-id-locations 193 | *_archive 194 | 195 | # flymake-mode 196 | *_flymake.* 197 | 198 | # eshell files 199 | /eshell/history 200 | /eshell/lastdir 201 | 202 | # elpa packages 203 | /elpa/ 204 | 205 | # reftex files 206 | *.rel 207 | 208 | # AUCTeX auto folder 209 | /auto/ 210 | 211 | # cask packages 212 | .cask/ 213 | dist/ 214 | 215 | # Flycheck 216 | flycheck_*.el 217 | 218 | # server auth directory 219 | /server/ 220 | 221 | # projectiles files 222 | .projectile 223 | 224 | # directory configuration 225 | .dir-locals.el 226 | 227 | # network security 228 | /network-security.data 229 | 230 | ##### SublimeText 231 | # Cache files for Sublime Text 232 | *.tmlanguage.cache 233 | *.tmPreferences.cache 234 | *.stTheme.cache 235 | 236 | # Workspace files are user-specific 237 | *.sublime-workspace 238 | 239 | # Project files should be checked into the repository, unless a significant 240 | # proportion of contributors will probably not be using Sublime Text 241 | # *.sublime-project 242 | 243 | # SFTP configuration file 244 | sftp-config.json 245 | sftp-config-alt*.json 246 | 247 | # Package control specific files 248 | Package Control.last-run 249 | Package Control.ca-list 250 | Package Control.ca-bundle 251 | Package Control.system-ca-bundle 252 | Package Control.cache/ 253 | Package Control.ca-certs/ 254 | Package Control.merged-ca-bundle 255 | Package Control.user-ca-bundle 256 | oscrypto-ca-bundle.crt 257 | bh_unicode_properties.cache 258 | 259 | # Sublime-github package stores a github token in this file 260 | # https://packagecontrol.io/packages/sublime-github 261 | GitHub.sublime-settings 262 | 263 | ##### Notepad++ 264 | # Notepad++ backups # 265 | *.bak 266 | 267 | ##### TextMate 268 | *.tmproj 269 | *.tmproject 270 | tmtags 271 | 272 | ##### VisualStudioCode 273 | .vscode/* 274 | !.vscode/settings.json 275 | !.vscode/tasks.json 276 | !.vscode/launch.json 277 | !.vscode/extensions.json 278 | *.code-workspace 279 | 280 | # Local History for Visual Studio Code 281 | .history/ 282 | 283 | ##### NetBeans 284 | **/nbproject/private/ 285 | **/nbproject/Makefile-*.mk 286 | **/nbproject/Package-*.bash 287 | build/ 288 | nbbuild/ 289 | dist/ 290 | nbdist/ 291 | .nb-gradle/ 292 | 293 | ##### JetBrains 294 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 295 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 296 | 297 | # User-specific stuff 298 | .idea/**/workspace.xml 299 | .idea/**/tasks.xml 300 | .idea/**/usage.statistics.xml 301 | .idea/**/dictionaries 302 | .idea/**/shelf 303 | 304 | # Generated files 305 | .idea/**/contentModel.xml 306 | 307 | # Sensitive or high-churn files 308 | .idea/**/dataSources/ 309 | .idea/**/dataSources.ids 310 | .idea/**/dataSources.local.xml 311 | .idea/**/sqlDataSources.xml 312 | .idea/**/dynamic.xml 313 | .idea/**/uiDesigner.xml 314 | .idea/**/dbnavigator.xml 315 | 316 | # Gradle 317 | .idea/**/gradle.xml 318 | .idea/**/libraries 319 | 320 | # Gradle and Maven with auto-import 321 | # When using Gradle or Maven with auto-import, you should exclude module files, 322 | # since they will be recreated, and may cause churn. Uncomment if using 323 | # auto-import. 324 | # .idea/artifacts 325 | # .idea/compiler.xml 326 | # .idea/jarRepositories.xml 327 | # .idea/modules.xml 328 | # .idea/*.iml 329 | # .idea/modules 330 | # *.iml 331 | # *.ipr 332 | 333 | # CMake 334 | cmake-build-*/ 335 | 336 | # Mongo Explorer plugin 337 | .idea/**/mongoSettings.xml 338 | 339 | # File-based project format 340 | *.iws 341 | 342 | # IntelliJ 343 | out/ 344 | 345 | # mpeltonen/sbt-idea plugin 346 | .idea_modules/ 347 | 348 | # JIRA plugin 349 | atlassian-ide-plugin.xml 350 | 351 | # Cursive Clojure plugin 352 | .idea/replstate.xml 353 | 354 | # Crashlytics plugin (for Android Studio and IntelliJ) 355 | com_crashlytics_export_strings.xml 356 | crashlytics.properties 357 | crashlytics-build.properties 358 | fabric.properties 359 | 360 | # Editor-based Rest Client 361 | .idea/httpRequests 362 | 363 | # Android studio 3.1+ serialized cache file 364 | .idea/caches/build_file_checksums.ser 365 | 366 | ##### Eclipse 367 | .metadata 368 | bin/ 369 | tmp/ 370 | *.tmp 371 | *.bak 372 | *.swp 373 | *~.nib 374 | local.properties 375 | .settings/ 376 | .loadpath 377 | .recommenders 378 | 379 | # External tool builders 380 | .externalToolBuilders/ 381 | 382 | # Locally stored "Eclipse launch configurations" 383 | *.launch 384 | 385 | # PyDev specific (Python IDE for Eclipse) 386 | *.pydevproject 387 | 388 | # CDT-specific (C/C++ Development Tooling) 389 | .cproject 390 | 391 | # CDT- autotools 392 | .autotools 393 | 394 | # Java annotation processor (APT) 395 | .factorypath 396 | 397 | # PDT-specific (PHP Development Tools) 398 | .buildpath 399 | 400 | # sbteclipse plugin 401 | .target 402 | 403 | # Tern plugin 404 | .tern-project 405 | 406 | # TeXlipse plugin 407 | .texlipse 408 | 409 | # STS (Spring Tool Suite) 410 | .springBeans 411 | 412 | # Code Recommenders 413 | .recommenders/ 414 | 415 | # Annotation Processing 416 | .apt_generated/ 417 | .apt_generated_test/ 418 | 419 | # Scala IDE specific (Scala & Java development for Eclipse) 420 | .cache-main 421 | .scala_dependencies 422 | .worksheet 423 | 424 | # Uncomment this line if you wish to ignore the project description file. 425 | # Typically, this file would be tracked if it contains build/dependency configurations: 426 | #.project 427 | 428 | ##### Dreamweaver 429 | # DW Dreamweaver added files 430 | _notes 431 | _compareTemp 432 | configs/ 433 | dwsync.xml 434 | dw_php_codehinting.config 435 | *.mno 436 | 437 | ##### CodeKit 438 | # General CodeKit files to ignore 439 | config.codekit 440 | config.codekit3 441 | /min 442 | 443 | ##### Gradle 444 | .gradle 445 | **/build/ 446 | !src/**/build/ 447 | 448 | # Ignore Gradle GUI config 449 | gradle-app.setting 450 | 451 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 452 | !gradle-wrapper.jar 453 | 454 | # Cache of project 455 | .gradletasknamecache 456 | 457 | # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 458 | # gradle/wrapper/gradle-wrapper.properties 459 | 460 | ##### Composer 461 | composer.phar 462 | /vendor/ 463 | 464 | # Commit your application's lock file https://getcomposer.org/doc/01-basic-usage.md#commit-your-composer-lock-file-to-version-control 465 | # You may choose to ignore a library lock file http://getcomposer.org/doc/02-libraries.md#lock-file 466 | composer.lock 467 | 468 | ##### PHP CodeSniffer 469 | # gitignore for the PHP Codesniffer framework 470 | # website: https://github.com/squizlabs/PHP_CodeSniffer 471 | # 472 | # Recommended template: PHP.gitignore 473 | 474 | /wpcs/* 475 | 476 | ##### SASS 477 | .sass-cache/ 478 | *.css.map 479 | *.sass.map 480 | *.scss.map 481 | 482 | 483 | .env 484 | Homestead.yaml 485 | Homestead.json 486 | /.vagrant 487 | .phpunit.result.cache 488 | tests/.phpunit.result.cache 489 | .php_cs.cache --------------------------------------------------------------------------------