* * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ namespace Chill\MainBundle\Phonenumber; use GuzzleHttp\Client; use GuzzleHttp\Exception\ClientException; use GuzzleHttp\Exception\ServerException; use GuzzleHttp\Exception\ConnectException; use Psr\Log\LoggerInterface; use Psr\Cache\CacheItemPoolInterface; /** * Helper to some task linked to phonenumber. * * Currently, only Twilio is supported (https://www.twilio.com/lookup). A method * allow to check if the helper is configured for validation. This should be used * before doing some validation. * * */ class PhonenumberHelper { /** * * @var Client */ protected $twilioClient; /** * * @var LoggerInterface */ protected $logger; /** * * @var CacheItemPoolInterface */ protected $cachePool; const LOOKUP_URI = 'https://lookups.twilio.com/v1/PhoneNumbers/%s'; const FORMAT_URI = 'https://lookups.twilio.com/v1/PhoneNumbers/%s'; public function __construct( CacheItemPoolInterface $cachePool, $config, LoggerInterface $logger ) { $this->logger = $logger; $this->cachePool = $cachePool; if (\array_key_exists('twilio_sid', $config) && !empty($config['twilio_sid']) && \array_key_exists('twilio_secret', $config) && !empty($config['twilio_secret'])) { $this->twilioClient = new Client([ 'auth' => [ $config['twilio_sid'], $config['twilio_secret'] ] ]); } } /** * Return true if the validation is configured and available. * * @return bool */ public function isPhonenumberValidationConfigured() : bool { return NULL !== $this->twilioClient; } /** * REturn true if the phoennumber is a mobile phone. Return always false * if the validation is not configured. * * @param string $phonenumber * @return bool */ public function isValidPhonenumberMobile($phonenumber) : bool { $validation = $this->performTwilioLookup($phonenumber); if (NULL === $validation) { return false; } return $validation === 'mobile'; } /** * Return true if the phonenumber is a landline or voip phone. Return always false * if the validation is not configured. * * @param string $phonenumber * @return bool */ public function isValidPhonenumberLandOrVoip($phonenumber) : bool { $validation = $this->performTwilioLookup($phonenumber); if (NULL === $validation) { return false; } return \in_array($validation, [ 'landline', 'voip' ]); } /** * Return true if the phonenumber is a landline or voip phone. Return always false * if the validation is not configured. * * @param string $phonenumber * @return bool */ public function isValidPhonenumberAny($phonenumber) : bool { $validation = $this->performTwilioLookup($phonenumber); if (NULL === $validation) { return false; } return \in_array($validation, [ 'landline', 'voip', 'mobile' ]); } public function format($phonenumber) { return $this->performTwilioFormat($phonenumber); } protected function performTwilioFormat($phonenumber) { if (FALSE === $this->isPhonenumberValidationConfigured()) { return $phonenumber; } // filter only number $filtered = \preg_replace("/[^0-9]/", "", $phonenumber); $item = $this->cachePool->getItem('pnum_format_nat_'.$filtered); if ($item->isHit()) { return $item->get(); } try { $response = $this->twilioClient->get(sprintf(self::FORMAT_URI, '+'.$filtered), [ 'http_errors' => true, ]); } catch (ClientException $e) { $this->logger->error("[phonenumber helper] Could not format number " . "due to client error", [ "message" => $e->getResponseBodySummary($e->getResponse()), "status_code" => $e->getResponse()->getStatusCode(), "phonenumber" => $phonenumber ]); return $phonenumber; } catch (ServerException $e) { $this->logger->error("[phonenumber helper] Could not format number " . "due to server error", [ "message" => $e->getResponseBodySummary($e->getResponse()), "status_code" => $e->getResponse()->getStatusCode(), "phonenumber" => $phonenumber ]); return null; } catch (ConnectException $e) { $this->logger->error("[phonenumber helper] Could not format number " . "due to connect error", [ "message" => $e->getMessage(), "phonenumber" => $phonenumber ]); return null; } $format = \json_decode($response->getBody())->national_format; $item ->set($format) // expires after 3d ->expiresAfter(3600 * 24 * 3) ; $this->cachePool->save($item); return $format; } protected function performTwilioLookup($phonenumber) { if (FALSE === $this->isPhonenumberValidationConfigured()) { return null; } // filter only number $filtered = \preg_replace("/[^0-9]/", "", $phonenumber); $item = $this->cachePool->getItem('pnum_'.$filtered); if ($item->isHit()) { return $item->get(); } try { $response = $this->twilioClient->get(sprintf(self::LOOKUP_URI, '+'.$filtered), [ 'http_errors' => true, 'query' => [ 'Type' => 'carrier' ] ]); } catch (ClientException $e) { return 'invalid'; } catch (ServerException $e) { $this->logger->error("[phonenumber helper] Could not perform validation " . "due to server error", [ "message" => $e->getResponseBodySummary($e->getResponse()), "status_code" => $e->getResponse()->getStatusCode(), "phonenumber" => $phonenumber ]); return null; } catch (ConnectException $e) { $this->logger->error("[phonenumber helper] Could not format number " . "due to connect error", [ "message" => $e->getMessage(), "phonenumber" => $phonenumber ]); return null; } $validation = \json_decode($response->getBody())->carrier->type; $item ->set($validation) // expires after 12h ->expiresAfter(3600 * 12) ; $this->cachePool->save($item); return $validation; } }