diff --git a/DependencyInjection/ChillMainExtension.php b/DependencyInjection/ChillMainExtension.php
index be61a3d49..c2e6f58e5 100644
--- a/DependencyInjection/ChillMainExtension.php
+++ b/DependencyInjection/ChillMainExtension.php
@@ -85,6 +85,9 @@ class ChillMainExtension extends Extension implements PrependExtensionInterface,
$container->setParameter('chill_main.redis',
$config['redis']);
+ $container->setParameter('chill_main.phone_helper',
+ $config['phone_helper'] ?? []);
+
// add the key 'widget' without the key 'enable'
$container->setParameter('chill_main.widgets',
isset($config['widgets']['homepage']) ?
@@ -109,6 +112,7 @@ class ChillMainExtension extends Extension implements PrependExtensionInterface,
$loader->load('services/notification.yml');
$loader->load('services/redis.yml');
$loader->load('services/command.yml');
+ $loader->load('services/phonenumber.yml');
}
public function getConfiguration(array $config, ContainerBuilder $container)
diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php
index 6971ff9eb..7909bbd01 100644
--- a/DependencyInjection/Configuration.php
+++ b/DependencyInjection/Configuration.php
@@ -85,6 +85,17 @@ class Configuration implements ConfigurationInterface
->end()
->end()
->end() // end of notifications
+ ->arrayNode('phone_helper')
+ ->canBeUnset()
+ ->children()
+ ->scalarNode('twilio_sid')
+ ->defaultNull()
+ ->end()
+ ->scalarNode('twilio_secret')
+ ->defaultNull()
+ ->end()
+ ->end()
+ ->end()
->arrayNode('redis')
->children()
->scalarNode('host')
diff --git a/Phonenumber/PhonenumberHelper.php b/Phonenumber/PhonenumberHelper.php
new file mode 100644
index 000000000..772b43945
--- /dev/null
+++ b/Phonenumber/PhonenumberHelper.php
@@ -0,0 +1,171 @@
+
+ *
+ * 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 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';
+
+
+ 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' ]);
+ }
+
+ 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;
+ }
+
+ $validation = \json_decode($response->getBody())->carrier->type;
+
+ $item
+ ->set($validation)
+ // expires after 12h
+ ->expiresAfter(3600 * 12)
+ ;
+
+ $this->cachePool->save($item);
+
+ return $validation;
+ }
+}
diff --git a/Resources/config/services/phonenumber.yml b/Resources/config/services/phonenumber.yml
new file mode 100644
index 000000000..ec519c836
--- /dev/null
+++ b/Resources/config/services/phonenumber.yml
@@ -0,0 +1,13 @@
+services:
+ Chill\MainBundle\Phonenumber\PhonenumberHelper:
+ arguments:
+ $logger: '@Psr\Log\LoggerInterface'
+ $config: '%chill_main.phone_helper%'
+ $cachePool: '@cache.user_data'
+
+ Chill\MainBundle\Validation\Validator\ValidPhonenumber:
+ arguments:
+ $logger: '@Psr\Log\LoggerInterface'
+ $phonenumberHelper: '@Chill\MainBundle\Phonenumber\PhonenumberHelper'
+ tags:
+ - { name: validator.constraint_validator }
diff --git a/Resources/translations/validators.fr.yml b/Resources/translations/validators.fr.yml
index db3c2e0ab..ab3596c79 100644
--- a/Resources/translations/validators.fr.yml
+++ b/Resources/translations/validators.fr.yml
@@ -12,4 +12,8 @@ A permission is already present for the same role and scope: Une permission est
"{{ username }} is not allowed to see entities published in this circle": "{{ username }} n'est pas autorisé à voir l'élément publié dans ce cercle."
#password request
-This username or email does not exists: Cet utilisateur ou email n'est pas présent dans la base de donnée
\ No newline at end of file
+This username or email does not exists: Cet utilisateur ou email n'est pas présent dans la base de donnée
+
+#phonenumber
+This is not a landline phonenumber: Ce numéro n'est pas une ligne fixe valide
+This is not a mobile phonenumber: Ce numéro n'est pas un numéro de portable valide
diff --git a/Validation/Constraint/PhonenumberConstraint.php b/Validation/Constraint/PhonenumberConstraint.php
new file mode 100644
index 000000000..9e322285d
--- /dev/null
+++ b/Validation/Constraint/PhonenumberConstraint.php
@@ -0,0 +1,43 @@
+
+ *
+ * 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\Validation\Constraint;
+
+use Symfony\Component\Validator\Constraint;
+
+/**
+ *
+ *
+ */
+class PhonenumberConstraint extends Constraint
+{
+ public $notMobileMessage = "This is not a mobile phonenumber";
+
+ public $notLandlineMessage = "This is not a landline phonenumber";
+
+ /**
+ * The type of phone: landline (not able to receive sms) or mobile (can receive sms)
+ *
+ * @var string 'landline' or 'mobile'
+ */
+ public $type = null;
+
+ public function validatedBy()
+ {
+ return \Chill\MainBundle\Validation\Validator\ValidPhonenumber::class;
+ }
+}
diff --git a/Validation/Validator/ValidPhonenumber.php b/Validation/Validator/ValidPhonenumber.php
new file mode 100644
index 000000000..971dedc25
--- /dev/null
+++ b/Validation/Validator/ValidPhonenumber.php
@@ -0,0 +1,83 @@
+
+ *
+ * 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\Validation\Validator;
+
+use Symfony\Component\Validator\ConstraintValidator;
+use Symfony\Component\Validator\Constraint;
+use Chill\MainBundle\Phonenumber\PhonenumberHelper;
+use Psr\Log\LoggerInterface;
+
+/**
+ *
+ *
+ */
+class ValidPhonenumber extends ConstraintValidator
+{
+ /**
+ *
+ * @var PhonenumberHelper
+ */
+ protected $phonenumberHelper;
+
+ protected $logger;
+
+ public function __construct(
+ LoggerInterface $logger,
+ PhonenumberHelper $phonenumberHelper
+ ) {
+ $this->phonenumberHelper = $phonenumberHelper;
+ $this->logger = $logger;
+ }
+
+ /**
+ *
+ * @param string $value
+ * @param \Chill\MainBundle\Validation\Constraint\PhonenumberConstraint $constraint
+ */
+ public function validate($value, Constraint $constraint)
+ {
+ if (FALSE === $this->phonenumberHelper->isPhonenumberValidationConfigured()) {
+ $this->logger->debug('[phonenumber] skipping validation due to not configured helper');
+
+ return;
+ }
+
+ if (empty($value)) {
+ return;
+ }
+
+ switch($constraint->type) {
+ case 'landline':
+ $isValid = $this->phonenumberHelper->isValidPhonenumberLandOrVoip($value);
+ $message = $constraint->notLandlineMessage;
+ break;
+ case 'mobile':
+ $isValid = $this->phonenumberHelper->isValidPhonenumberMobile($value);
+ $message = $constraint->notMobileMessage;
+ break;
+
+ default:
+ throw new \LogicException(sprintf("This type '%s' is not implemented. "
+ . "Possible values are 'mobile', 'landline'"), $constraint->type);
+ }
+
+ if (FALSE === $isValid) {
+ $this->context->addViolation($message, [ '%phonenumber%' => $value ]);
+ }
+ }
+}