From c4bc7d94c1d88a2f2d94191563dce6f19dfce6d8 Mon Sep 17 00:00:00 2001 From: osaajani Date: Thu, 9 Jan 2020 22:23:58 +0100 Subject: [PATCH] Improve adapters datas interface by defining fields and implement real OVH SMS API adapter ! --- adapters/AdapterInterface.php | 16 +- adapters/OvhSmsAdapter.php | 188 +++++++++++++++++++- adapters/TestAdapter.php | 19 +- composer.json | 3 +- composer.lock | 316 +++++++++++++++++++++++++++++++++- controllers/publics/Phone.php | 39 ++++- templates/phone/add.php | 58 +++---- 7 files changed, 591 insertions(+), 48 deletions(-) diff --git a/adapters/AdapterInterface.php b/adapters/AdapterInterface.php index 18c2bff..3d7e7c3 100755 --- a/adapters/AdapterInterface.php +++ b/adapters/AdapterInterface.php @@ -31,6 +31,12 @@ * Description of the datas expected by the adapter to help the user. (e.g : A list of expecteds Api credentials fields, with name and value) */ public static function meta_datas_help() : string; + + /** + * List of entries we want in datas for the adapter + * @return array : Eachline line is a field as an array with keys : name, title, description, required + */ + public static function meta_datas_fields() : array; /** * Does the implemented service support flash smss @@ -58,7 +64,7 @@ * @param bool $flash : Is the SMS a Flash SMS * @return mixed Uid of the sended message if send, False else */ - public function send (string $destination, string $text, bool $flash); + public function send (string $destination, string $text, bool $flash = false); /** @@ -68,6 +74,14 @@ public function read () : array; + /** + * Method called to verify if the adapter is working correctly + * should be use for exemple to verify that credentials and number are both valid + * @return boolean : False on error, true else + */ + public function test () : bool; + + /** * Method called on reception of a status update notification for a SMS * @return mixed : False on error, else array ['uid' => uid of the sms, 'status' => New status of the sms ('unknown', 'delivered', 'failed')] diff --git a/adapters/OvhSmsAdapter.php b/adapters/OvhSmsAdapter.php index bf48c55..96f14e4 100755 --- a/adapters/OvhSmsAdapter.php +++ b/adapters/OvhSmsAdapter.php @@ -1,5 +1,6 @@ 'app_key', + 'title' => 'Application Key', + 'description' => 'Paramètre "Application Key" obtenu lors de la génération de la clef API OVH.', + 'required' => true, + ], + [ + 'name' => 'app_secret', + 'title' => 'Application Secret', + 'description' => 'Paramètre "Application Secret" obtenu lors de la génération de la clef API OVH.', + 'required' => true, + ], + [ + 'name' => 'consumer_key', + 'title' => 'Consumer Key', + 'description' => 'Paramètre "Consumer Key" obtenu lors de la génération de la clef API OVH.', + 'required' => true, + ], + [ + 'name' => 'endpoint', + 'title' => 'Endpoint', + 'description' => 'Endpoint de l\'API OVH, voir https://github.com/ovh/php-ovh/#supported-apis.', + 'required' => true, + ], + [ + 'name' => 'service_name', + 'title' => 'Service Name', + 'description' => 'Service Name de votre service SMS chez OVH. Il s\'agit du nom associé à votre service SMS dans la console OVH, probablement quelque chose comme "sms-xxxxx-1" ou "xxxx" est votre identifiant client OVH.', + 'required' => true, + ], + ]; + } /** * Does the implemented service support flash smss @@ -53,6 +94,15 @@ */ private $datas; + /** + * OVH Api instance + */ + private $api; + + /** + * Number formated to be compatible with http query according to the ovh way + */ + private $formatted_number; /** * Adapter constructor, called when instanciated by RaspiSMS @@ -62,7 +112,15 @@ public function __construct (string $number, string $datas) { $this->number = $number; - $this->datas = $datas; + $this->formatted_number = str_replace('+', '00', $number); + $this->datas = json_decode($datas, true); + + $this->api = new Api( + $this->datas['app_key'], + $this->datas['app_secret'], + $this->datas['endpoint'], + $this->datas['consumer_key'] + ); } @@ -73,9 +131,33 @@ * @param bool $flash : Is the SMS a Flash SMS * @return mixed Uid of the sended message if send, False else */ - public function send (string $destination, string $text, bool $flash) + public function send (string $destination, string $text, bool $flash = false) { - return uniqid(); + try + { + $success = true; + + $endpoint = '/sms/' . $this->datas['service_name'] . '/virtualNumbers/' . $this->formatted_number . '/jobs'; + $params = [ + 'message' => $text, + 'receivers' => [$destination], + ]; + + $response = $this->api->post($endpoint, $params); + + $nb_invalid_receivers = count(($response['invalidReceivers'] ?? [])); + if ($nb_invalid_receivers > 0) + { + return false; + } + + $uids = $response['ids'] ?? []; + return ($uids[0] ?? false); + } + catch (\Exception $e) + { + return false; + } } @@ -85,7 +167,77 @@ */ public function read () : array { - return []; + try + { + $success = true; + + $endpoint = '/sms/' . $this->datas['service_name'] . '/virtualNumbers/' . $this->formatted_number . '/incoming'; + $uids = $this->api->get($endpoint); + + if (!is_array($uids) || !$uids) + { + return []; + } + + $received_smss = []; + foreach ($uids as $uid) + { + $endpoint = '/sms/' . $this->datas['service_name'] . '/virtualNumbers/' . $this->formatted_number . '/incoming/' . $uid; + $sms_details = $this->api->get($endpoint); + + if (!isset($sms_details['creationDatetime'], $sms_details['message'], $sms_details['sender'])) + { + continue; + } + + $received_smss[] = [ + 'at' => (new \DateTime($sms_details['creationDatetime']))->format('Y-m-d H:i:s'), + 'text' => $sms_details['message'], + 'origin' => $sms_details['sender'], + 'destination' => $this->number, + ]; + + //Remove the sms to prevent double reading as ovh do not offer a filter for unread messages only + $endpoint = '/sms/' . $this->datas['service_name'] . '/virtualNumbers/' . $this->formatted_number . '/incoming/' . $uid; + $this->api->delete($endpoint); + } + + return $received_smss; + } + catch (\Exception $e) + { + return []; + } + } + + + /** + * Method called to verify if the adapter is working correctly + * should be use for exemple to verify that credentials and number are both valid + * @return boolean : False on error, true else + */ + public function test () : bool + { + try + { + $success = true; + + //Check service name + $endpoint = '/sms/' . $this->datas['service_name']; + $response = $this->api->get($endpoint); + $success = $success && (bool) $response; + + //Check virtualnumber + $endpoint = '/sms/virtualNumbers/' . $this->formatted_number; + $response = $this->api->get($endpoint); + $success = $success && (bool) $response; + + return $success; + } + catch (\Exception $e) + { + return false; + } } @@ -95,6 +247,30 @@ */ public static function status_change_callback () { - return false; + $uid = $_GET['id'] ?? false; + $dlr = $_GET['dlr'] ?? false; + + if ($uid === false || $dlr ==== false) + { + return false; + } + + switch ($dlr) + { + case 1: + $status = 'delivered'; + break; + + case 2: + case 16: + $status = 'failed'; + break; + + default: + $status = 'unknown'; + break; + } + + return ['uid' => $uid, 'status' => $status]; } } diff --git a/adapters/TestAdapter.php b/adapters/TestAdapter.php index a0affae..cbd3b23 100755 --- a/adapters/TestAdapter.php +++ b/adapters/TestAdapter.php @@ -31,6 +31,12 @@ * Description of the datas expected by the adapter to help the user. (e.g : A list of expecteds Api credentials fields, with name and value) */ public static function meta_datas_help() : string { return 'No datas.'; } + + /** + * List of entries we want in datas for the adapter + * @return array : Eachline line is a field as an array with keys : name, title, description, required + */ + public static function meta_datas_fields() : array { return []; } /** * Does the implemented service support flash smss @@ -84,7 +90,7 @@ * @param bool $flash : Is the SMS a Flash SMS * @return mixed Uid of the sended message if send, False else */ - public function send (string $destination, string $text, bool $flash) + public function send (string $destination, string $text, bool $flash = false) { $uid = uniqid(); @@ -125,6 +131,17 @@ } + /** + * Method called to verify if the adapter is working correctly + * should be use for exemple to verify that credentials and number are both valid + * @return boolean : False on error, true else + */ + public function test () : bool + { + return true; + } + + /** * Method called on reception of a status update notification for a SMS * @return mixed : False on error, else array ['uid' => uid of the sms, 'status' => New status of the sms ('unknown', 'delivered', 'failed')] diff --git a/composer.json b/composer.json index 4329035..37fe220 100755 --- a/composer.json +++ b/composer.json @@ -6,7 +6,8 @@ "twig/twig": "^3.0", "symfony/expression-language": "^5.0", "robmorgan/phinx": "^0.11.1", - "monolog/monolog": "^2.0" + "monolog/monolog": "^2.0", + "ovh/ovh": "^2.0" }, "require-dev": { } diff --git a/composer.lock b/composer.lock index f33bc37..fe332fb 100755 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "2c1bb9c8a11ff3457ba8db4d5d1d33ae", + "content-hash": "c1b16bab1d0052b5ccb38c18a08ee0d2", "packages": [ { "name": "ajani/flash-message", @@ -495,6 +495,195 @@ "description": "Locale functions required by libphonenumber-for-php", "time": "2019-10-09T18:53:14+00:00" }, + { + "name": "guzzlehttp/guzzle", + "version": "6.5.2", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "43ece0e75098b7ecd8d13918293029e555a50f82" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/43ece0e75098b7ecd8d13918293029e555a50f82", + "reference": "43ece0e75098b7ecd8d13918293029e555a50f82", + "shasum": "" + }, + "require": { + "ext-json": "*", + "guzzlehttp/promises": "^1.0", + "guzzlehttp/psr7": "^1.6.1", + "php": ">=5.5" + }, + "require-dev": { + "ext-curl": "*", + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.4 || ^7.0", + "psr/log": "^1.1" + }, + "suggest": { + "ext-intl": "Required for Internationalized Domain Name (IDN) support", + "psr/log": "Required for using the Log middleware" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "6.5-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "homepage": "http://guzzlephp.org/", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "rest", + "web service" + ], + "time": "2019-12-23T11:57:10+00:00" + }, + { + "name": "guzzlehttp/promises", + "version": "v1.3.1", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/a59da6cf61d80060647ff4d3eb2c03a2bc694646", + "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646", + "shasum": "" + }, + "require": { + "php": ">=5.5.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Guzzle promises library", + "keywords": [ + "promise" + ], + "time": "2016-12-20T10:07:11+00:00" + }, + { + "name": "guzzlehttp/psr7", + "version": "1.6.1", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "239400de7a173fe9901b9ac7c06497751f00727a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/239400de7a173fe9901b9ac7c06497751f00727a", + "reference": "239400de7a173fe9901b9ac7c06497751f00727a", + "shasum": "" + }, + "require": { + "php": ">=5.4.0", + "psr/http-message": "~1.0", + "ralouphie/getallheaders": "^2.0.5 || ^3.0.0" + }, + "provide": { + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "ext-zlib": "*", + "phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.8" + }, + "suggest": { + "zendframework/zend-httphandlerrunner": "Emit PSR-7 responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.6-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Schultze", + "homepage": "https://github.com/Tobion" + } + ], + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "http", + "message", + "psr-7", + "request", + "response", + "stream", + "uri", + "url" + ], + "time": "2019-07-01T23:21:34+00:00" + }, { "name": "ingenerator/tokenista", "version": "v1.1.0", @@ -629,6 +818,41 @@ ], "time": "2019-11-13T10:27:43+00:00" }, + { + "name": "ovh/ovh", + "version": "v2.0.1", + "source": { + "type": "git", + "url": "https://github.com/ovh/php-ovh.git", + "reference": "25a97501a150dff8a7848c372b5826395aab4df2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ovh/php-ovh/zipball/25a97501a150dff8a7848c372b5826395aab4df2", + "reference": "25a97501a150dff8a7848c372b5826395aab4df2", + "shasum": "" + }, + "require": { + "guzzlehttp/guzzle": "^6.0" + }, + "require-dev": { + "phpdocumentor/phpdocumentor": "2.*", + "phpunit/phpunit": "4.*", + "squizlabs/php_codesniffer": "1.*" + }, + "type": "library", + "autoload": { + "psr-4": { + "Ovh\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Wrapper for OVH APIs", + "time": "2016-02-10T13:16:38+00:00" + }, { "name": "psr/cache", "version": "1.0.1", @@ -724,6 +948,56 @@ ], "time": "2017-02-14T16:28:37+00:00" }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "time": "2016-08-06T14:39:51+00:00" + }, { "name": "psr/log", "version": "1.1.2", @@ -819,6 +1093,46 @@ ], "time": "2017-10-23T01:57:42+00:00" }, + { + "name": "ralouphie/getallheaders", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/ralouphie/getallheaders.git", + "reference": "120b605dfeb996808c31b6477290a714d356e822" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", + "reference": "120b605dfeb996808c31b6477290a714d356e822", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^5 || ^6.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/getallheaders.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ralph Khattar", + "email": "ralph.khattar@gmail.com" + } + ], + "description": "A polyfill for getallheaders.", + "time": "2019-03-08T08:55:37+00:00" + }, { "name": "robmorgan/phinx", "version": "0.11.1", diff --git a/controllers/publics/Phone.php b/controllers/publics/Phone.php index 97a9672..5094d18 100755 --- a/controllers/publics/Phone.php +++ b/controllers/publics/Phone.php @@ -101,7 +101,7 @@ class Phone extends \descartes\Controller * @param $csrf : CSRF token * @param string $_POST['number'] : Phone number * @param string $_POST['adapter'] : Phone adapter - * @param string $_POST['adapter_datas'] : Phone adapter datas + * @param array $_POST['adapter_datas'] : Phone adapter datas */ public function create($csrf) { @@ -114,7 +114,7 @@ class Phone extends \descartes\Controller $id_user = $_SESSION['user']['id']; $number = $_POST['number'] ?? false; $adapter = $_POST['adapter'] ?? false; - $adapter_datas = !empty($_POST['adapter_datas']) ? $_POST['adapter_datas'] : '{}'; + $adapter_datas = !empty($_POST['adapter_datas']) ? $_POST['adapter_datas'] : []; if (!$number || !$adapter) { @@ -139,26 +139,49 @@ class Phone extends \descartes\Controller $adapters = $this->internal_adapter->list_adapters(); - $adapter_exists = false; + $find_adapter = false; foreach ($adapters as $metas) { if ($metas['meta_classname'] === $adapter) { - $adapter_exists = true; + $find_adapter = $metas; break; } } - if (!$adapter_exists) + if (!$find_adapter) { \FlashMessage\FlashMessage::push('danger', 'Cet adaptateur n\'existe pas.'); return $this->redirect(\descartes\Router::url('Phone', 'add')); } - - if (NULL === json_decode($adapter_datas)) + //If missing required data fields, error + foreach ($find_adapter['meta_datas_fields'] as $field) { - \FlashMessage\FlashMessage::push('danger', 'La chaîne de configuration n\'est pas valide.'); + if ($field['required'] === false) + { + continue; + } + + if (!empty($adapter_datas[$field['name']])) + { + continue; + } + + \FlashMessage\FlashMessage::push('danger', 'Vous n\'avez pas rempli certains champs obligatoires pour l\'adaptateur choisis.'); + return $this->redirect(\descartes\Router::url('Phone', 'add')); + } + + $adapter_datas = json_encode($adapter_datas); + + //Check adapter is working correctly with thoses numbers and datas + $adapter_classname = $find_adapter['meta_classname']; + $adapter_instance = new $adapter_classname($number, $adapter_datas); + $adapter_working = $adapter_instance->test(); + + if (!$adapter_working) + { + \FlashMessage\FlashMessage::push('danger', 'Impossible d\'utiliser l\'adaptateur choisis avec les données fournies. Vérifiez le numéro de téléphone et les réglages.'); return $this->redirect(\descartes\Router::url('Phone', 'add')); } diff --git a/templates/phone/add.php b/templates/phone/add.php index 5c280ae..ade6093 100755 --- a/templates/phone/add.php +++ b/templates/phone/add.php @@ -59,20 +59,15 @@ title="s($adapter['meta_description']); ?>" data-description="s($adapter['meta_description']); ?>" data-datas-help="s($adapter['meta_datas_help']); ?>" + data-datas-fields="s(json_encode($adapter['meta_datas_fields'])); ?>" > s($adapter['meta_name']); ?> -
- -

- Les données à fournir à l'adaptateur pour lui permettre de faire la liaison avec le téléphone. Par exemple des identifiants d'API.
-

- - -
+
+
Annuler @@ -84,36 +79,39 @@