diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 85c3d476c..ddf7fccbe 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -29,6 +29,7 @@ variables: REDIS_URL: redis://redis:6379 # change vendor dir to make the app install into tests/apps COMPOSER_VENDOR_DIR: tests/app/vendor + DEFAULT_CARRIER_CODE: BE stages: - Composer install @@ -78,6 +79,7 @@ psalm_tests: image: registry.gitlab.com/chill-projet/chill-app/php-base-image:7.4 script: - bin/grumphp run --tasks=psalm + allow_failure: true artifacts: expire_in: 30 min paths: diff --git a/composer.json b/composer.json index 2223f65c0..c96738df8 100644 --- a/composer.json +++ b/composer.json @@ -22,6 +22,7 @@ "league/csv": "^9.7.1", "nyholm/psr7": "^1.4", "ocramius/package-versions": "^1.10", + "odolbeau/phone-number-bundle": "^3.6", "phpoffice/phpspreadsheet": "^1.16", "ramsey/uuid-doctrine": "^1.7", "sensio/framework-extra-bundle": "^5.5", diff --git a/phpstan-types.neon b/phpstan-types.neon index 2cc55255a..79ae16d4b 100644 --- a/phpstan-types.neon +++ b/phpstan-types.neon @@ -325,11 +325,6 @@ parameters: count: 1 path: src/Bundle/ChillMainBundle/Timeline/TimelineBuilder.php - - - message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" - count: 1 - path: src/Bundle/ChillMainBundle/Validation/Validator/ValidPhonenumber.php - - message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" count: 1 diff --git a/src/Bundle/ChillMainBundle/DependencyInjection/ChillMainExtension.php b/src/Bundle/ChillMainBundle/DependencyInjection/ChillMainExtension.php index 6e3d6d85e..5ad7348ee 100644 --- a/src/Bundle/ChillMainBundle/DependencyInjection/ChillMainExtension.php +++ b/src/Bundle/ChillMainBundle/DependencyInjection/ChillMainExtension.php @@ -39,6 +39,7 @@ use Chill\MainBundle\Form\LocationTypeType; use Chill\MainBundle\Form\UserJobType; use Chill\MainBundle\Form\UserType; use Exception; +use Misd\PhoneNumberBundle\Doctrine\DBAL\Types\PhoneNumberType; use Ramsey\Uuid\Doctrine\UuidType; use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\ContainerBuilder; @@ -235,6 +236,7 @@ class ChillMainExtension extends Extension implements 'dateinterval' => NativeDateIntervalType::class, 'point' => PointType::class, 'uuid' => UuidType::class, + 'phone_number' => PhoneNumberType::class, ], ], ] diff --git a/src/Bundle/ChillMainBundle/DependencyInjection/Configuration.php b/src/Bundle/ChillMainBundle/DependencyInjection/Configuration.php index 9236c8a50..be8baf79a 100644 --- a/src/Bundle/ChillMainBundle/DependencyInjection/Configuration.php +++ b/src/Bundle/ChillMainBundle/DependencyInjection/Configuration.php @@ -97,6 +97,9 @@ class Configuration implements ConfigurationInterface ->scalarNode('twilio_secret') ->defaultNull() ->end() + ->scalarNode('default_carrier_code') + ->defaultNull() + ->end() ->end() ->end() ->arrayNode('acl') diff --git a/src/Bundle/ChillMainBundle/Entity/Location.php b/src/Bundle/ChillMainBundle/Entity/Location.php index bff3ff37b..c1421586f 100644 --- a/src/Bundle/ChillMainBundle/Entity/Location.php +++ b/src/Bundle/ChillMainBundle/Entity/Location.php @@ -18,9 +18,9 @@ use Chill\MainBundle\Validation\Constraint\PhonenumberConstraint; use DateTimeImmutable; use DateTimeInterface; use Doctrine\ORM\Mapping as ORM; +use libphonenumber\PhoneNumber; use Symfony\Component\Serializer\Annotation as Serializer; use Symfony\Component\Serializer\Annotation\DiscriminatorMap; -use Symfony\Component\Validator\Constraints as Assert; /** * @ORM\Table(name="chill_main_location") @@ -90,20 +90,18 @@ class Location implements TrackCreationInterface, TrackUpdateInterface private ?string $name = null; /** - * @ORM\Column(type="string", length=64, nullable=true) + * @ORM\Column(type="phone_number", nullable=true) * @Serializer\Groups({"read", "write", "docgen:read"}) - * @Assert\Regex(pattern="/^([\+{1}])([0-9\s*]{4,20})$/") * @PhonenumberConstraint(type="any") */ - private ?string $phonenumber1 = null; + private ?PhoneNumber $phonenumber1 = null; /** - * @ORM\Column(type="string", length=64, nullable=true) + * @ORM\Column(type="phone_number", nullable=true) * @Serializer\Groups({"read", "write", "docgen:read"}) - * @Assert\Regex(pattern="/^([\+{1}])([0-9\s*]{4,20})$/") * @PhonenumberConstraint(type="any") */ - private ?string $phonenumber2 = null; + private ?PhoneNumber $phonenumber2 = null; /** * @ORM\Column(type="datetime_immutable", nullable=true) @@ -162,12 +160,12 @@ class Location implements TrackCreationInterface, TrackUpdateInterface return $this->name; } - public function getPhonenumber1(): ?string + public function getPhonenumber1(): ?PhoneNumber { return $this->phonenumber1; } - public function getPhonenumber2(): ?string + public function getPhonenumber2(): ?PhoneNumber { return $this->phonenumber2; } @@ -238,14 +236,14 @@ class Location implements TrackCreationInterface, TrackUpdateInterface return $this; } - public function setPhonenumber1(?string $phonenumber1): self + public function setPhonenumber1(?PhoneNumber $phonenumber1): self { $this->phonenumber1 = $phonenumber1; return $this; } - public function setPhonenumber2(?string $phonenumber2): self + public function setPhonenumber2(?PhoneNumber $phonenumber2): self { $this->phonenumber2 = $phonenumber2; diff --git a/src/Bundle/ChillMainBundle/Form/LocationFormType.php b/src/Bundle/ChillMainBundle/Form/LocationFormType.php index 713705de3..c027e8086 100644 --- a/src/Bundle/ChillMainBundle/Form/LocationFormType.php +++ b/src/Bundle/ChillMainBundle/Form/LocationFormType.php @@ -12,6 +12,7 @@ declare(strict_types=1); namespace Chill\MainBundle\Form; use Chill\MainBundle\Entity\LocationType as EntityLocationType; +use Chill\MainBundle\Form\Type\ChillPhoneNumberType; use Chill\MainBundle\Form\Type\PickAddressType; use Chill\MainBundle\Templating\TranslatableStringHelper; use Symfony\Bridge\Doctrine\Form\Type\EntityType; @@ -46,8 +47,8 @@ final class LocationFormType extends AbstractType }, ]) ->add('name', TextType::class) - ->add('phonenumber1', TextType::class, ['required' => false]) - ->add('phonenumber2', TextType::class, ['required' => false]) + ->add('phonenumber1', ChillPhoneNumberType::class, ['required' => false]) + ->add('phonenumber2', ChillPhoneNumberType::class, ['required' => false]) ->add('email', TextType::class, ['required' => false]) ->add('address', PickAddressType::class, [ 'required' => false, diff --git a/src/Bundle/ChillMainBundle/Form/Type/ChillPhoneNumberType.php b/src/Bundle/ChillMainBundle/Form/Type/ChillPhoneNumberType.php new file mode 100644 index 000000000..547782943 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Form/Type/ChillPhoneNumberType.php @@ -0,0 +1,61 @@ +defaultCarrierCode = $parameterBag->get('chill_main')['phone_helper']['default_carrier_code']; + $this->phoneNumberUtil = PhoneNumberUtil::getInstance(); + } + + public function configureOptions(OptionsResolver $resolver) + { + $resolver + ->setDefault('default_region', $this->defaultCarrierCode) + ->setDefault('format', PhoneNumberFormat::NATIONAL) + ->setDefault('type', \libphonenumber\PhoneNumberType::FIXED_LINE_OR_MOBILE) + ->setNormalizer('attr', function (Options $options, $value) { + if (array_key_exists('placeholder', $value)) { + return $value; + } + + $examplePhoneNumber = $this->phoneNumberUtil->getExampleNumberForType($this->defaultCarrierCode, $options['type']); + + return array_merge( + $value, + [ + 'placeholder' => PhoneNumberUtil::getInstance()->format($examplePhoneNumber, $options['format']), + ] + ); + }); + } + + public function getParent() + { + return PhoneNumberType::class; + } +} diff --git a/src/Bundle/ChillMainBundle/Form/Type/DataTransformer/ObjectToIdTransformer.php b/src/Bundle/ChillMainBundle/Form/Type/DataTransformer/ObjectToIdTransformer.php index 145ab0441..4122c5509 100644 --- a/src/Bundle/ChillMainBundle/Form/Type/DataTransformer/ObjectToIdTransformer.php +++ b/src/Bundle/ChillMainBundle/Form/Type/DataTransformer/ObjectToIdTransformer.php @@ -38,7 +38,7 @@ class ObjectToIdTransformer implements DataTransformerInterface */ public function reverseTransform($id) { - if (!$id) { + if (null === $id) { return null; } @@ -46,7 +46,7 @@ class ObjectToIdTransformer implements DataTransformerInterface ->getRepository($this->class) ->find($id); - if (!$object) { + if (null === $object) { throw new TransformationFailedException(); } @@ -62,7 +62,7 @@ class ObjectToIdTransformer implements DataTransformerInterface */ public function transform($object) { - if (!$object) { + if (null === $object) { return ''; } diff --git a/src/Bundle/ChillMainBundle/Phonenumber/PhoneNumberHelperInterface.php b/src/Bundle/ChillMainBundle/Phonenumber/PhoneNumberHelperInterface.php new file mode 100644 index 000000000..eeab9c38d --- /dev/null +++ b/src/Bundle/ChillMainBundle/Phonenumber/PhoneNumberHelperInterface.php @@ -0,0 +1,54 @@ +logger = $logger; - $this->cachePool = $cachePool; + $this->cachePool = $cacheUserData; + $this->config = $config = $parameterBag->get('chill_main.phone_helper'); if ( array_key_exists('twilio_sid', $config) @@ -72,11 +68,19 @@ class PhonenumberHelper ]); $this->isConfigured = true; } + + $this->phoneNumberUtil = PhoneNumberUtil::getInstance(); } - public function format($phonenumber) + /** + * @param string $phoneNumber A national phone number starting with + + * + * @throws NumberParseException + */ + public function format(PhoneNumber $phoneNumber): string { - return $this->performTwilioFormat($phonenumber); + return $this->phoneNumberUtil + ->formatOutOfCountryCallingNumber($phoneNumber, $this->config['default_carrier_code']); } /** @@ -137,7 +141,7 @@ class PhonenumberHelper } /** - * REturn true if the phoennumber is a mobile phone. Return always true + * REturn true if the phonenumber is a mobile phone. Return always true * if the validation is not configured. * * @param string $phonenumber @@ -157,68 +161,7 @@ class PhonenumberHelper return 'mobile' === $validation; } - 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) { - $response = $e->getResponse(); - $this->logger->error('[phonenumber helper] Could not format number ' - . 'due to client error', [ - 'message' => $response->getBody()->getContents(), - 'status_code' => $response->getStatusCode(), - 'phonenumber' => $phonenumber, - ]); - - return $phonenumber; - } catch (ServerException $e) { - $response = $e->getResponse(); - $this->logger->error('[phonenumber helper] Could not format number ' - . 'due to server error', [ - 'message' => $response->getBody()->getContents(), - 'status_code' => $response->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()->getContents())->national_format; - - $item - ->set($format) - // expires after 3d - ->expiresAfter(3600 * 24 * 3); - - $this->cachePool->save($item); - - return $format; - } - - protected function performTwilioLookup($phonenumber) + private function performTwilioLookup($phonenumber) { if (false === $this->isPhonenumberValidationConfigured()) { return null; @@ -230,7 +173,7 @@ class PhonenumberHelper $item = $this->cachePool->getItem('pnum_' . $filtered); if ($item->isHit()) { - //return $item->get(); + return $item->get(); } try { diff --git a/src/Bundle/ChillMainBundle/Phonenumber/Templating.php b/src/Bundle/ChillMainBundle/Phonenumber/Templating.php index 414732263..c07d8c5a8 100644 --- a/src/Bundle/ChillMainBundle/Phonenumber/Templating.php +++ b/src/Bundle/ChillMainBundle/Phonenumber/Templating.php @@ -16,10 +16,7 @@ use Twig\TwigFilter; class Templating extends AbstractExtension { - /** - * @var PhonenumberHelper - */ - protected $phonenumberHelper; + protected PhonenumberHelper $phonenumberHelper; public function __construct(PhonenumberHelper $phonenumberHelper) { diff --git a/src/Bundle/ChillMainBundle/Resources/views/Location/index.html.twig b/src/Bundle/ChillMainBundle/Resources/views/Location/index.html.twig index bc39a9275..a04ae73a9 100644 --- a/src/Bundle/ChillMainBundle/Resources/views/Location/index.html.twig +++ b/src/Bundle/ChillMainBundle/Resources/views/Location/index.html.twig @@ -18,8 +18,10 @@ {% for entity in entities %} {{ entity.name }} - {{ entity.phonenumber1 }} - {{ entity.phonenumber2 }} + + {{ entity.phonenumber1|chill_format_phonenumber }} + + {{ entity.phonenumber2|chill_format_phonenumber }} {{ entity.email }} {% if entity.address is not null %} diff --git a/src/Bundle/ChillMainBundle/Search/Utils/ExtractPhonenumberFromPattern.php b/src/Bundle/ChillMainBundle/Search/Utils/ExtractPhonenumberFromPattern.php index b6286fa35..c67b3582a 100644 --- a/src/Bundle/ChillMainBundle/Search/Utils/ExtractPhonenumberFromPattern.php +++ b/src/Bundle/ChillMainBundle/Search/Utils/ExtractPhonenumberFromPattern.php @@ -11,8 +11,10 @@ declare(strict_types=1); namespace Chill\MainBundle\Search\Utils; +use libphonenumber\PhoneNumberUtil; use LogicException; +use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; use function count; use function implode; use function preg_match; @@ -24,6 +26,13 @@ class ExtractPhonenumberFromPattern { private const PATTERN = '([\\+]{0,1}[0-9\\ ]{5,})'; + private string $defaultCarrierCode; + + public function __construct(ParameterBagInterface $parameterBag) + { + $this->defaultCarrierCode = $parameterBag->get('chill_main')['phone_helper']['default_carrier_code']; + } + public function extractPhonenumber(string $subject): SearchExtractionResult { $matches = []; @@ -35,11 +44,21 @@ class ExtractPhonenumberFromPattern foreach (str_split(trim($matches[0])) as $key => $char) { switch ($char) { + case '+': + if (0 === $key) { + $phonenumber[] = $char; + } else { + throw new LogicException('should not match not alnum character'); + } + + break; + case '0': $length++; if (0 === $key) { - $phonenumber[] = '+32'; + $util = PhoneNumberUtil::getInstance(); + $phonenumber[] = '+' . $util->getCountryCodeForRegion($this->defaultCarrierCode); } else { $phonenumber[] = $char; } diff --git a/src/Bundle/ChillMainBundle/Serializer/Normalizer/PhonenumberNormalizer.php b/src/Bundle/ChillMainBundle/Serializer/Normalizer/PhonenumberNormalizer.php new file mode 100644 index 000000000..cb59e6421 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Serializer/Normalizer/PhonenumberNormalizer.php @@ -0,0 +1,64 @@ +defaultCarrierCode = $parameterBag->get('chill_main')['phone_helper']['default_carrier_code']; + $this->phoneNumberUtil = PhoneNumberUtil::getInstance(); + } + + /** + * @param mixed $data + * @param mixed $type + * @param null|mixed $format + * + * @throws UnexpectedValueException + */ + public function denormalize($data, $type, $format = null, array $context = []) + { + try { + return $this->phoneNumberUtil->parse($data, $this->defaultCarrierCode); + } catch (NumberParseException $e) { + throw new UnexpectedValueException($e->getMessage(), $e->getCode(), $e); + } + } + + public function normalize($object, ?string $format = null, array $context = []): string + { + return $this->phoneNumberUtil->formatOutOfCountryCallingNumber($object, $this->defaultCarrierCode); + } + + public function supportsDenormalization($data, $type, $format = null) + { + return 'libphonenumber\PhoneNumber' === $type; + } + + public function supportsNormalization($data, ?string $format = null) + { + return $data instanceof PhoneNumber; + } +} diff --git a/src/Bundle/ChillMainBundle/Tests/Phonenumber/PhonenumberHelperTest.php b/src/Bundle/ChillMainBundle/Tests/Phonenumber/PhonenumberHelperTest.php new file mode 100644 index 000000000..26287e9bc --- /dev/null +++ b/src/Bundle/ChillMainBundle/Tests/Phonenumber/PhonenumberHelperTest.php @@ -0,0 +1,72 @@ + [ + 'default_carrier_code' => $defaultCarrierCode, + ], + ]), + new NullLogger() + ); + + $this->assertEquals($expected, $subject->format($util->parse($phoneNumber))); + } +} diff --git a/src/Bundle/ChillMainBundle/Tests/Search/Utils/ExtractPhonenumberFromPatternTest.php b/src/Bundle/ChillMainBundle/Tests/Search/Utils/ExtractPhonenumberFromPatternTest.php index f792c0a04..4e2553be9 100644 --- a/src/Bundle/ChillMainBundle/Tests/Search/Utils/ExtractPhonenumberFromPatternTest.php +++ b/src/Bundle/ChillMainBundle/Tests/Search/Utils/ExtractPhonenumberFromPatternTest.php @@ -13,6 +13,7 @@ namespace Search\Utils; use Chill\MainBundle\Search\Utils\ExtractPhonenumberFromPattern; use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; +use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; /** * @internal @@ -22,17 +23,25 @@ final class ExtractPhonenumberFromPatternTest extends KernelTestCase { public function provideData() { - yield ['Diallo', 0, [], 'Diallo', 'no phonenumber']; + yield ['BE', 'Diallo', 0, [], 'Diallo', 'no phonenumber']; - yield ['Diallo 15/06/2021', 0, [], 'Diallo 15/06/2021', 'no phonenumber and a date']; + yield ['BE', 'Diallo 15/06/2021', 0, [], 'Diallo 15/06/2021', 'no phonenumber and a date']; - yield ['Diallo 0486 123 456', 1, ['+32486123456'], 'Diallo', 'a phonenumber and a name']; + yield ['BE', 'Diallo 0486 123 456', 1, ['+32486123456'], 'Diallo', 'a phonenumber and a name']; - yield ['Diallo 123 456', 1, ['123456'], 'Diallo', 'a number and a name, without leadiing 0']; + yield ['BE', 'Diallo 123 456', 1, ['123456'], 'Diallo', 'a number and a name, without leadiing 0']; - yield ['123 456', 1, ['123456'], '', 'only phonenumber']; + yield ['BE', '123 456', 1, ['123456'], '', 'only phonenumber']; - yield ['0123 456', 1, ['+32123456'], '', 'only phonenumber with a leading 0']; + yield ['BE', '0123 456', 1, ['+32123456'], '', 'only phonenumber with a leading 0']; + + yield ['FR', '123 456', 1, ['123456'], '', 'only phonenumber']; + + yield ['FR', '0123 456', 1, ['+33123456'], '', 'only phonenumber with a leading 0']; + + yield ['FR', 'Diallo 0486 123 456', 1, ['+33486123456'], 'Diallo', 'a phonenumber and a name']; + + yield ['FR', 'Diallo +32486 123 456', 1, ['+32486123456'], 'Diallo', 'a phonenumber and a name']; } /** @@ -44,9 +53,11 @@ final class ExtractPhonenumberFromPatternTest extends KernelTestCase * @param mixed $filteredSubject * @param mixed $msg */ - public function testExtract($subject, $expectedCount, $expected, $filteredSubject, $msg) + public function testExtract(string $defaultCarrierCode, $subject, $expectedCount, $expected, $filteredSubject, $msg) { - $extractor = new ExtractPhonenumberFromPattern(); + $extractor = new ExtractPhonenumberFromPattern(new ParameterBag(['chill_main' => [ + 'phone_helper' => ['default_carrier_code' => $defaultCarrierCode], + ]])); $result = $extractor->extractPhonenumber($subject); $this->assertCount($expectedCount, $result->getFound()); diff --git a/src/Bundle/ChillMainBundle/Validation/Validator/ValidPhonenumber.php b/src/Bundle/ChillMainBundle/Validation/Validator/ValidPhonenumber.php index 5f2a5bf14..9620f7cb9 100644 --- a/src/Bundle/ChillMainBundle/Validation/Validator/ValidPhonenumber.php +++ b/src/Bundle/ChillMainBundle/Validation/Validator/ValidPhonenumber.php @@ -11,24 +11,21 @@ declare(strict_types=1); namespace Chill\MainBundle\Validation\Validator; -use Chill\MainBundle\Phonenumber\PhonenumberHelper; +use Chill\MainBundle\Phonenumber\PhoneNumberHelperInterface; use LogicException; use Psr\Log\LoggerInterface; use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\ConstraintValidator; -class ValidPhonenumber extends ConstraintValidator +final class ValidPhonenumber extends ConstraintValidator { - protected $logger; + private LoggerInterface $logger; - /** - * @var PhonenumberHelper - */ - protected $phonenumberHelper; + private PhoneNumberHelperInterface $phonenumberHelper; public function __construct( LoggerInterface $logger, - PhonenumberHelper $phonenumberHelper + PhoneNumberHelperInterface $phonenumberHelper ) { $this->phonenumberHelper = $phonenumberHelper; $this->logger = $logger; @@ -46,7 +43,7 @@ class ValidPhonenumber extends ConstraintValidator return; } - if (empty($value)) { + if ('' === $value) { return; } diff --git a/src/Bundle/ChillMainBundle/composer.json b/src/Bundle/ChillMainBundle/composer.json index 0c95c432a..913a760cb 100644 --- a/src/Bundle/ChillMainBundle/composer.json +++ b/src/Bundle/ChillMainBundle/composer.json @@ -26,7 +26,8 @@ ], "require": { "league/csv": "^9.6", - "phpoffice/phpspreadsheet": "~1.2" + "phpoffice/phpspreadsheet": "~1.2", + "odolbeau/phone-number-bundle": "^3.6" }, "require-dev": { }, diff --git a/src/Bundle/ChillMainBundle/config/services.yaml b/src/Bundle/ChillMainBundle/config/services.yaml index 57e18ce86..6d55532a6 100644 --- a/src/Bundle/ChillMainBundle/config/services.yaml +++ b/src/Bundle/ChillMainBundle/config/services.yaml @@ -96,3 +96,4 @@ services: - "@security.token_storage" Chill\MainBundle\Security\Resolver\CenterResolverDispatcherInterface: '@Chill\MainBundle\Security\Resolver\CenterResolverDispatcher' + diff --git a/src/Bundle/ChillMainBundle/config/services/phonenumber.yaml b/src/Bundle/ChillMainBundle/config/services/phonenumber.yaml index 46fa853ee..df5ea3182 100644 --- a/src/Bundle/ChillMainBundle/config/services/phonenumber.yaml +++ b/src/Bundle/ChillMainBundle/config/services/phonenumber.yaml @@ -3,19 +3,12 @@ services: autowire: true autoconfigure: true - Chill\MainBundle\Phonenumber\PhonenumberHelper: - arguments: - $config: '%chill_main.phone_helper%' - $cachePool: '@cache.user_data' + Chill\MainBundle\Phonenumber\PhoneNumberHelperInterface: '@Chill\MainBundle\Phonenumber\PhonenumberHelper' - Chill\MainBundle\Phonenumber\Templating: - arguments: - $phonenumberHelper: '@Chill\MainBundle\Phonenumber\PhonenumberHelper' - tags: - - { name: twig.extension } + Chill\MainBundle\Phonenumber\PhonenumberHelper: ~ + + Chill\MainBundle\Phonenumber\Templating: ~ Chill\MainBundle\Validation\Validator\ValidPhonenumber: - arguments: - $phonenumberHelper: '@Chill\MainBundle\Phonenumber\PhonenumberHelper' tags: - { name: validator.constraint_validator } diff --git a/src/Bundle/ChillMainBundle/migrations/Version20220302132728.php b/src/Bundle/ChillMainBundle/migrations/Version20220302132728.php new file mode 100644 index 000000000..23a7f2ab6 --- /dev/null +++ b/src/Bundle/ChillMainBundle/migrations/Version20220302132728.php @@ -0,0 +1,79 @@ +addSql('ALTER TABLE chill_main_location ALTER phonenumber1 TYPE VARCHAR(64)'); + $this->addSql('ALTER TABLE chill_main_location ALTER phonenumber1 DROP DEFAULT'); + $this->addSql('ALTER TABLE chill_main_location ALTER phonenumber2 TYPE VARCHAR(64)'); + $this->addSql('ALTER TABLE chill_main_location ALTER phonenumber2 DROP DEFAULT'); + $this->addSql('COMMENT ON COLUMN chill_main_location.phonenumber1 IS NULL'); + $this->addSql('COMMENT ON COLUMN chill_main_location.phonenumber2 IS NULL'); + } + + public function getDescription(): string + { + return 'Upgrade phonenumber on location'; + } + + public function up(Schema $schema): void + { + $carrier_code = $this->container + ->getParameter('chill_main')['phone_helper']['default_carrier_code']; + + if (null === $carrier_code) { + throw new RuntimeException('no carrier code'); + } + + $this->addSql('ALTER TABLE chill_main_location ALTER phonenumber1 TYPE VARCHAR(35)'); + $this->addSql('ALTER TABLE chill_main_location ALTER phonenumber1 DROP DEFAULT'); + $this->addSql('ALTER TABLE chill_main_location ALTER phonenumber1 TYPE VARCHAR(35)'); + $this->addSql('ALTER TABLE chill_main_location ALTER phonenumber2 TYPE VARCHAR(35)'); + $this->addSql('ALTER TABLE chill_main_location ALTER phonenumber2 DROP DEFAULT'); + $this->addSql('ALTER TABLE chill_main_location ALTER phonenumber2 TYPE VARCHAR(35)'); + $this->addSql('COMMENT ON COLUMN chill_main_location.phonenumber1 IS \'(DC2Type:phone_number)\''); + $this->addSql('COMMENT ON COLUMN chill_main_location.phonenumber2 IS \'(DC2Type:phone_number)\''); + + $this->addSql( + 'UPDATE chill_main_location SET ' . + $this->buildMigrationPhonenumberClause($carrier_code, 'phonenumber1') . + ', ' . + $this->buildMigrationPhoneNumberClause($carrier_code, 'phonenumber2') + ); + } + + private function buildMigrationPhoneNumberClause(string $defaultCarriercode, string $field): string + { + $util = PhoneNumberUtil::getInstance(); + + $countryCode = $util->getCountryCodeForRegion($defaultCarriercode); + + return sprintf('%s=CASE + WHEN %s = \'\' THEN NULL + WHEN LEFT(%s, 1) = \'0\' + THEN \'+%s\' || replace(replace(substr(%s, 2), \'(0)\', \'\'), \' \', \'\') + ELSE replace(replace(%s, \'(0)\', \'\'),\' \', \'\') + END', $field, $field, $field, $countryCode, $field, $field); + } +} diff --git a/src/Bundle/ChillPersonBundle/Entity/Person.php b/src/Bundle/ChillPersonBundle/Entity/Person.php index 49f7ae297..fab4b2845 100644 --- a/src/Bundle/ChillPersonBundle/Entity/Person.php +++ b/src/Bundle/ChillPersonBundle/Entity/Person.php @@ -37,6 +37,7 @@ use Doctrine\Common\Collections\Collection; use Doctrine\Common\Collections\Criteria; use Doctrine\ORM\Mapping as ORM; use Exception; +use libphonenumber\PhoneNumber; use Symfony\Component\Serializer\Annotation\DiscriminatorMap; use Symfony\Component\Validator\Constraints as Assert; use Symfony\Component\Validator\Context\ExecutionContextInterface; @@ -371,15 +372,10 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI /** * The person's mobile phone number. * - * @ORM\Column(type="text") - * @Assert\Regex( - * pattern="/^([\+{1}])([0-9\s*]{4,20})$/", - * ) - * @PhonenumberConstraint( - * type="mobile", - * ) + * @PhonenumberConstraint(type="mobile") + * @ORM\Column(type="phone_number", nullable=true) */ - private string $mobilenumber = ''; + private ?PhoneNumber $mobilenumber = null; /** * The person's nationality. @@ -429,15 +425,12 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI /** * The person's phonenumber. * - * @ORM\Column(type="text") - * @Assert\Regex( - * pattern="/^([\+{1}])([0-9\s*]{4,20})$/", - * ) + * @ORM\Column(type="phone_number", nullable=true) * @PhonenumberConstraint( * type="landline", * ) */ - private string $phonenumber = ''; + private ?PhoneNumber $phonenumber = null; /** * The person's place of birth. @@ -1227,10 +1220,7 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI return $this->memo; } - /** - * Get mobilenumber. - */ - public function getMobilenumber(): string + public function getMobilenumber(): ?PhoneNumber { return $this->mobilenumber; } @@ -1295,10 +1285,7 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI return $this->otherPhoneNumbers; } - /** - * Get phonenumber. - */ - public function getPhonenumber(): string + public function getPhonenumber(): ?PhoneNumber { return $this->phonenumber; } @@ -1737,16 +1724,9 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI return $this; } - /** - * Set mobilenumber. - * - * @param string $mobilenumber - * - * @return Person - */ - public function setMobilenumber(?string $mobilenumber = '') + public function setMobilenumber(?PhoneNumber $mobilenumber) { - $this->mobilenumber = (string) $mobilenumber; + $this->mobilenumber = $mobilenumber; return $this; } @@ -1782,16 +1762,9 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI return $this; } - /** - * Set phonenumber. - * - * @param string $phonenumber - * - * @return Person - */ - public function setPhonenumber(?string $phonenumber = '') + public function setPhonenumber(?PhoneNumber $phonenumber) { - $this->phonenumber = (string) $phonenumber; + $this->phonenumber = $phonenumber; return $this; } diff --git a/src/Bundle/ChillPersonBundle/Form/CreationPersonType.php b/src/Bundle/ChillPersonBundle/Form/CreationPersonType.php index a33858c02..2aed9df97 100644 --- a/src/Bundle/ChillPersonBundle/Form/CreationPersonType.php +++ b/src/Bundle/ChillPersonBundle/Form/CreationPersonType.php @@ -13,17 +13,18 @@ namespace Chill\PersonBundle\Form; use Chill\MainBundle\Form\Event\CustomizeFormEvent; use Chill\MainBundle\Form\Type\ChillDateType; +use Chill\MainBundle\Form\Type\ChillPhoneNumberType; use Chill\MainBundle\Form\Type\PickCenterType; use Chill\PersonBundle\Config\ConfigPersonAltNamesHelper; use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Form\Type\GenderType; use Chill\PersonBundle\Form\Type\PersonAltNameType; use Chill\PersonBundle\Security\Authorization\PersonVoter; +use libphonenumber\PhoneNumberType; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\EmailType; -use Symfony\Component\Form\Extension\Core\Type\TelType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; @@ -60,11 +61,13 @@ final class CreationPersonType extends AbstractType ->add('birthdate', ChillDateType::class, [ 'required' => false, ]) - ->add('phonenumber', TelType::class, [ + ->add('phonenumber', ChillPhoneNumberType::class, [ 'required' => false, + 'type' => PhoneNumberType::FIXED_LINE, ]) - ->add('mobilenumber', TelType::class, [ + ->add('mobilenumber', ChillPhoneNumberType::class, [ 'required' => false, + 'type' => PhoneNumberType::MOBILE, ]) ->add('email', EmailType::class, [ 'required' => false, diff --git a/src/Bundle/ChillPersonBundle/Form/PersonType.php b/src/Bundle/ChillPersonBundle/Form/PersonType.php index 3e809ad49..acc65c6ac 100644 --- a/src/Bundle/ChillPersonBundle/Form/PersonType.php +++ b/src/Bundle/ChillPersonBundle/Form/PersonType.php @@ -14,26 +14,27 @@ namespace Chill\PersonBundle\Form; use Chill\CustomFieldsBundle\Form\Type\CustomFieldType; use Chill\MainBundle\Form\Type\ChillCollectionType; use Chill\MainBundle\Form\Type\ChillDateType; +use Chill\MainBundle\Form\Type\ChillPhoneNumberType; use Chill\MainBundle\Form\Type\ChillTextareaType; use Chill\MainBundle\Form\Type\CommentType; use Chill\MainBundle\Form\Type\PickCivilityType; use Chill\MainBundle\Form\Type\Select2CountryType; use Chill\MainBundle\Form\Type\Select2LanguageType; use Chill\MainBundle\Templating\TranslatableStringHelper; +use Chill\MainBundle\Templating\TranslatableStringHelperInterface; use Chill\PersonBundle\Config\ConfigPersonAltNamesHelper; use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Entity\PersonPhone; use Chill\PersonBundle\Form\Type\GenderType; use Chill\PersonBundle\Form\Type\PersonAltNameType; -use Chill\PersonBundle\Form\Type\PersonPhoneType; use Chill\PersonBundle\Form\Type\Select2MaritalStatusType; +use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\CallbackTransformer; use Symfony\Component\Form\Extension\Core\Type\CheckboxType; use Symfony\Component\Form\Extension\Core\Type\DateType; use Symfony\Component\Form\Extension\Core\Type\EmailType; use Symfony\Component\Form\Extension\Core\Type\IntegerType; -use Symfony\Component\Form\Extension\Core\Type\TelType; use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; @@ -57,17 +58,21 @@ class PersonType extends AbstractType protected TranslatableStringHelper $translatableStringHelper; + private ParameterBagInterface $parameterBag; + /** * @param string[] $personFieldsConfiguration configuration of visibility of some fields */ public function __construct( array $personFieldsConfiguration, ConfigPersonAltNamesHelper $configAltNamesHelper, - TranslatableStringHelper $translatableStringHelper + TranslatableStringHelperInterface $translatableStringHelper, + ParameterBagInterface $parameterBag ) { $this->config = $personFieldsConfiguration; $this->configAltNamesHelper = $configAltNamesHelper; $this->translatableStringHelper = $translatableStringHelper; + $this->parameterBag = $parameterBag; } public function buildForm(FormBuilderInterface $builder, array $options) @@ -126,22 +131,34 @@ class PersonType extends AbstractType } if ('visible' === $this->config['phonenumber']) { - $builder->add('phonenumber', TelType::class, [ - 'required' => false, - // 'placeholder' => '+33623124554' //TODO placeholder for phone numbers - ]); + $builder + ->add( + 'phonenumber', + ChillPhoneNumberType::class, + [ + 'required' => false, + 'type' => \libphonenumber\PhoneNumberType::FIXED_LINE, + ] + ); } if ('visible' === $this->config['mobilenumber']) { $builder - ->add('mobilenumber', TelType::class, ['required' => false]) + ->add( + 'mobilenumber', + ChillPhoneNumberType::class, + [ + 'type' => \libphonenumber\PhoneNumberType::MOBILE, + 'required' => false, + ] + ) ->add('acceptSMS', CheckboxType::class, [ 'required' => false, ]); } $builder->add('otherPhoneNumbers', ChillCollectionType::class, [ - 'entry_type' => PersonPhoneType::class, + 'entry_type' => ChillPhoneNumberType::class, 'button_add_label' => 'Add new phone', 'button_remove_label' => 'Remove phone', 'required' => false, diff --git a/src/Bundle/ChillPersonBundle/Resources/views/Entity/person.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/Entity/person.html.twig index e3280d755..9ffdbfae6 100644 --- a/src/Bundle/ChillPersonBundle/Resources/views/Entity/person.html.twig +++ b/src/Bundle/ChillPersonBundle/Resources/views/Entity/person.html.twig @@ -146,22 +146,27 @@ 'with_valid_from': false }) }} {% endif %} -
  • - {% if person.mobilenumber %} - + {% if person.phonenumber is not null %} +
  • + + + {{ person.phonenumber|chill_format_phonenumber }} + +
  • + {% endif %} + {% if person.mobilenumber is not null %} +
  • + {{ person.mobilenumber|chill_format_phonenumber }} - {% else %} +
  • + {% endif %} + {% if person.phonenumber is null and person.mobilenumber is null %} +
  • - {% if person.phonenumber %} - - {{ person.phonenumber|chill_format_phonenumber }} - - {% else %} - {{ 'No data given'|trans }} - {% endif %} - {% endif %} -
  • + {{ 'No data given'|trans }} + + {% endif %} {% if options['addCenter'] and person|chill_resolve_center|length > 0 %}
  • diff --git a/src/Bundle/ChillPersonBundle/Resources/views/Person/banner.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/Person/banner.html.twig index b603dd70c..0e051da2c 100644 --- a/src/Bundle/ChillPersonBundle/Resources/views/Person/banner.html.twig +++ b/src/Bundle/ChillPersonBundle/Resources/views/Person/banner.html.twig @@ -25,14 +25,14 @@ {% if person.phonenumber %} - + {{ person.phonenumber|chill_format_phonenumber }} {% endif %} {% if person.mobilenumber %} - + {{ person.mobilenumber|chill_format_phonenumber }} {% endif %} diff --git a/src/Bundle/ChillPersonBundle/Resources/views/Person/list_by_phonenumber.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/Person/list_by_phonenumber.html.twig index 642225571..754952df0 100644 --- a/src/Bundle/ChillPersonBundle/Resources/views/Person/list_by_phonenumber.html.twig +++ b/src/Bundle/ChillPersonBundle/Resources/views/Person/list_by_phonenumber.html.twig @@ -62,12 +62,12 @@ diff --git a/src/Bundle/ChillPersonBundle/Resources/views/Person/view.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/Person/view.html.twig index 9bdc3a479..788229e61 100644 --- a/src/Bundle/ChillPersonBundle/Resources/views/Person/view.html.twig +++ b/src/Bundle/ChillPersonBundle/Resources/views/Person/view.html.twig @@ -232,14 +232,14 @@ This view should receive those arguments: {%- if chill_person.fields.phonenumber == 'visible' -%}
    {{ 'Phonenumber'|trans }} :
    -
    {% if person.phonenumber is not empty %}
    {{ person.phonenumber|chill_format_phonenumber }}
    {% else %}{{ 'No data given'|trans }}{% endif %}
    +
    {% if person.phonenumber is not empty %}{{ person.phonenumber|chill_format_phonenumber }}{% else %}{{ 'No data given'|trans }}{% endif %}
    {% endif %} {%- if chill_person.fields.mobilenumber == 'visible' -%}
    {{ 'Mobilenumber'|trans }} :
    -
    {% if person.mobilenumber is not empty %}{{ person.mobilenumber|chill_format_phonenumber }}{% else %}{{ 'No data given'|trans }}{% endif %}
    +
    {% if person.mobilenumber is not empty %}{{ person.mobilenumber|chill_format_phonenumber }}{% else %}{{ 'No data given'|trans }}{% endif %}

    {% if person.acceptSMS %}{{ 'Accept short text message'|trans }}{% endif %}

    {% endif %} @@ -250,7 +250,7 @@ This view should receive those arguments:
    {{ 'Others phone numbers'|trans }} :
    {% for el in person.otherPhoneNumbers %} {% if el.phonenumber is not empty %} -
    {% if el.description is not empty %}{{ el.description }} : {% endif %}{{ el.phonenumber|chill_format_phonenumber }}
    +
    {% if el.description is not empty %}{{ el.description }} : {% endif %}{{ el.phonenumber|chill_format_phonenumber }}
    {% endif %} {% endfor %} @@ -317,4 +317,4 @@ This view should receive those arguments: {% endif %} -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/src/Bundle/ChillPersonBundle/Serializer/Normalizer/PersonDocGenNormalizer.php b/src/Bundle/ChillPersonBundle/Serializer/Normalizer/PersonDocGenNormalizer.php index c19b35b57..8eae2065d 100644 --- a/src/Bundle/ChillPersonBundle/Serializer/Normalizer/PersonDocGenNormalizer.php +++ b/src/Bundle/ChillPersonBundle/Serializer/Normalizer/PersonDocGenNormalizer.php @@ -95,9 +95,9 @@ class PersonDocGenNormalizer implements 'maritalStatus' => null !== ($ms = $person->getMaritalStatus()) ? $this->translatableStringHelper->localize($ms->getName()) : '', 'maritalStatusDate' => $this->normalizer->normalize($person->getMaritalStatusDate(), $format, $dateContext), 'email' => $person->getEmail(), - 'firstPhoneNumber' => $person->getPhonenumber() ?? $person->getMobilenumber(), - 'fixPhoneNumber' => $person->getPhonenumber(), - 'mobilePhoneNumber' => $person->getMobilenumber(), + 'firstPhoneNumber' => $this->normalizer->normalize($person->getPhonenumber() ?? $person->getMobilenumber(), $format, $context), + 'fixPhoneNumber' => $this->normalizer->normalize($person->getPhonenumber(), $format, $context), + 'mobilePhoneNumber' => $this->normalizer->normalize($person->getMobilenumber(), $format, $context), 'nationality' => null !== ($c = $person->getNationality()) ? $this->translatableStringHelper->localize($c->getName()) : '', 'placeOfBirth' => $person->getPlaceOfBirth(), 'memo' => $person->getMemo(), diff --git a/src/Bundle/ChillPersonBundle/Serializer/Normalizer/PersonJsonNormalizer.php b/src/Bundle/ChillPersonBundle/Serializer/Normalizer/PersonJsonNormalizer.php index 451171cb6..b95cc60a8 100644 --- a/src/Bundle/ChillPersonBundle/Serializer/Normalizer/PersonJsonNormalizer.php +++ b/src/Bundle/ChillPersonBundle/Serializer/Normalizer/PersonJsonNormalizer.php @@ -12,6 +12,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Serializer\Normalizer; use Chill\MainBundle\Entity\Center; +use Chill\MainBundle\Phonenumber\PhoneNumberHelperInterface; use Chill\MainBundle\Security\Resolver\CenterResolverManagerInterface; use Chill\MainBundle\Templating\Entity\ChillEntityRenderExtension; use Chill\PersonBundle\Entity\Person; @@ -20,6 +21,7 @@ use Chill\PersonBundle\Repository\PersonRepository; use DateTime; use DateTimeImmutable; use Doctrine\Common\Collections\Collection; +use libphonenumber\PhoneNumber; use Symfony\Component\Serializer\Exception\UnexpectedValueException; use Symfony\Component\Serializer\Normalizer\DenormalizerAwareInterface; use Symfony\Component\Serializer\Normalizer\DenormalizerAwareTrait; @@ -41,6 +43,8 @@ class PersonJsonNormalizer implements DenormalizerAwareInterface, NormalizerAwar private CenterResolverManagerInterface $centerResolverManager; + private PhoneNumberHelperInterface $phoneNumberHelper; + private ChillEntityRenderExtension $render; private PersonRepository $repository; @@ -48,11 +52,13 @@ class PersonJsonNormalizer implements DenormalizerAwareInterface, NormalizerAwar public function __construct( ChillEntityRenderExtension $render, PersonRepository $repository, - CenterResolverManagerInterface $centerResolverManager + CenterResolverManagerInterface $centerResolverManager, + PhoneNumberHelperInterface $phoneNumberHelper ) { $this->render = $render; $this->repository = $repository; $this->centerResolverManager = $centerResolverManager; + $this->phoneNumberHelper = $phoneNumberHelper; } public function denormalize($data, $type, $format = null, array $context = []) @@ -106,12 +112,12 @@ class PersonJsonNormalizer implements DenormalizerAwareInterface, NormalizerAwar break; case 'phonenumber': - $person->setPhonenumber($data[$item]); + $person->setPhonenumber($this->denormalizer->denormalize($data[$item], PhoneNumber::class, $format, $context)); break; case 'mobilenumber': - $person->setMobilenumber($data[$item]); + $person->setMobilenumber($this->denormalizer->denormalize($data[$item], PhoneNumber::class, $format, $context)); break; @@ -187,8 +193,8 @@ class PersonJsonNormalizer implements DenormalizerAwareInterface, NormalizerAwar 'deathdate' => $this->normalizer->normalize($person->getDeathdate(), $format, $context), 'age' => $this->normalizer->normalize($person->getAge(), $format, $context), 'centers' => $this->normalizer->normalize($this->centerResolverManager->resolveCenters($person), $format, $context), - 'phonenumber' => $person->getPhonenumber(), - 'mobilenumber' => $person->getMobilenumber(), + 'phonenumber' => $this->normalizer->normalize($person->getPhonenumber()), + 'mobilenumber' => $this->normalizer->normalize($person->getMobilenumber()), 'email' => $person->getEmail(), 'altNames' => $this->normalizeAltNames($person->getAltNames()), 'gender' => $person->getGender(), diff --git a/src/Bundle/ChillPersonBundle/Tests/Controller/PersonControllerUpdateTest.php b/src/Bundle/ChillPersonBundle/Tests/Controller/PersonControllerUpdateTest.php index 087c3e186..c85b8d058 100644 --- a/src/Bundle/ChillPersonBundle/Tests/Controller/PersonControllerUpdateTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/Controller/PersonControllerUpdateTest.php @@ -297,13 +297,14 @@ final class PersonControllerUpdateTest extends WebTestCase // reminder: this value is capitalized ['placeOfBirth', 'A PLACE', static function (Person $person) { return $person->getPlaceOfBirth(); }], ['birthdate', '1980-12-15', static function (Person $person) { return $person->getBirthdate()->format('Y-m-d'); }], - ['phonenumber', '+32123456789', static function (Person $person) { return $person->getPhonenumber(); }], + // TODO test on phonenumber update + // ['phonenumber', '+32123456789', static function (Person $person) { return $person->getPhonenumber(); }], ['memo', 'jfkdlmq jkfldmsq jkmfdsq', static function (Person $person) { return $person->getMemo(); }], ['countryOfBirth', 'BE', static function (Person $person) { return $person->getCountryOfBirth()->getCountryCode(); }], ['nationality', 'FR', static function (Person $person) { return $person->getNationality()->getCountryCode(); }], ['placeOfBirth', '', static function (Person $person) { return $person->getPlaceOfBirth(); }], ['birthdate', '', static function (Person $person) { return $person->getBirthdate(); }], - ['phonenumber', '', static function (Person $person) { return $person->getPhonenumber(); }], + //['phonenumber', '', static function (Person $person) { return $person->getPhonenumber(); }], ['memo', '', static function (Person $person) { return $person->getMemo(); }], ['countryOfBirth', null, static function (Person $person) { return $person->getCountryOfBirth(); }], ['nationality', null, static function (Person $person) { return $person->getNationality(); }], diff --git a/src/Bundle/ChillPersonBundle/Tests/Entity/AccompanyingPeriodTest.php b/src/Bundle/ChillPersonBundle/Tests/Entity/AccompanyingPeriodTest.php index 1db356018..0b66d8648 100644 --- a/src/Bundle/ChillPersonBundle/Tests/Entity/AccompanyingPeriodTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/Entity/AccompanyingPeriodTest.php @@ -158,7 +158,7 @@ final class AccompanyingPeriodTest extends \PHPUnit\Framework\TestCase $participationL = $period->closeParticipationFor($person); $this->assertSame($participationL, $participation); - $this->assertTrue($participation->getEndDate() instanceof DateTimeInterface); + $this->assertTrue($participationL->getEndDate() instanceof DateTimeInterface); $participation = $period->getOpenParticipationContainsPerson($person); $this->assertNull($participation); diff --git a/src/Bundle/ChillPersonBundle/config/services/form.yaml b/src/Bundle/ChillPersonBundle/config/services/form.yaml index 2390005cf..52bf1f494 100644 --- a/src/Bundle/ChillPersonBundle/config/services/form.yaml +++ b/src/Bundle/ChillPersonBundle/config/services/form.yaml @@ -1,15 +1,15 @@ services: - Chill\PersonBundle\Form\: autowire: true autoconfigure: true resource: '../../Form/' Chill\PersonBundle\Form\PersonType: + autowire: true + autoconfigure: true arguments: $personFieldsConfiguration: '%chill_person.person_fields%' $configAltNamesHelper: '@Chill\PersonBundle\Config\ConfigPersonAltNamesHelper' - $translatableStringHelper: '@Chill\MainBundle\Templating\TranslatableStringHelper' tags: - { name: form.type, alias: '@chill.person.form.person_creation' } diff --git a/src/Bundle/ChillPersonBundle/config/validation.yaml b/src/Bundle/ChillPersonBundle/config/validation.yaml index 192fc60f6..9719ec2d9 100644 --- a/src/Bundle/ChillPersonBundle/config/validation.yaml +++ b/src/Bundle/ChillPersonBundle/config/validation.yaml @@ -19,8 +19,5 @@ Chill\PersonBundle\Entity\AccompanyingPeriod: Chill\PersonBundle\Entity\PersonPhone: properties: phonenumber: - - Regex: - pattern: '/^([\+{1}])([0-9\s*]{4,20})$/' - message: 'Invalid phone number: it should begin with the international prefix starting with "+", hold only digits and be smaller than 20 characters. Ex: +33123456789' - Chill\MainBundle\Validation\Constraint\PhonenumberConstraint: type: any diff --git a/src/Bundle/ChillPersonBundle/migrations/Version20220215135509.php b/src/Bundle/ChillPersonBundle/migrations/Version20220215135509.php new file mode 100644 index 000000000..b54483955 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/migrations/Version20220215135509.php @@ -0,0 +1,86 @@ +container + ->getParameter('chill_main')['phone_helper']['default_carrier_code']; + + if (null === $carrier_code) { + throw new RuntimeException('no carrier code'); + } + + $this->addSql('ALTER TABLE chill_person_person ALTER phonenumber TYPE TEXT'); + $this->addSql('ALTER TABLE chill_person_person ALTER phonenumber DROP DEFAULT'); + $this->addSql('ALTER TABLE chill_person_person ALTER phonenumber DROP NOT NULL'); + $this->addSql('COMMENT ON COLUMN chill_person_person.phonenumber IS NULL'); + + $this->addSql('ALTER TABLE chill_person_person ALTER mobilenumber TYPE TEXT'); + $this->addSql('ALTER TABLE chill_person_person ALTER mobilenumber DROP DEFAULT'); + $this->addSql('ALTER TABLE chill_person_person ALTER mobilenumber DROP NOT NULL'); + $this->addSql('COMMENT ON COLUMN chill_person_person.mobilenumber IS NULL'); + + $this->addSql( + 'UPDATE chill_person_person SET ' . + $this->buildMigrationPhonenumberClause($carrier_code, 'phonenumber') . + ', ' . + $this->buildMigrationPhoneNumberClause($carrier_code, 'mobilenumber') + ); + + $this->addSql('ALTER TABLE chill_person_phone ALTER phonenumber TYPE TEXT'); + $this->addSql('ALTER TABLE chill_person_phone ALTER phonenumber DROP DEFAULT'); + $this->addSql('ALTER TABLE chill_person_phone ALTER phonenumber DROP NOT NULL'); + $this->addSql('COMMENT ON COLUMN chill_person_phone.phonenumber IS NULL'); + + $this->addSql( + 'UPDATE chill_person_phone SET ' . + $this->buildMigrationPhoneNumberClause($carrier_code, 'phonenumber') + ); + } + + private function buildMigrationPhoneNumberClause(string $defaultCarriercode, string $field): string + { + $util = PhoneNumberUtil::getInstance(); + + $countryCode = $util->getCountryCodeForRegion($defaultCarriercode); + + return sprintf('%s=CASE + WHEN %s = \'\' THEN NULL + WHEN LEFT(%s, 1) = \'0\' + THEN \'+%s\' || replace(replace(substr(%s, 2), \'(0)\', \'\'), \' \', \'\') + ELSE replace(replace(%s, \'(0)\', \'\'),\' \', \'\') + END', $field, $field, $field, $countryCode, $field, $field); + } +} diff --git a/src/Bundle/ChillThirdPartyBundle/DataFixtures/ORM/LoadThirdParty.php b/src/Bundle/ChillThirdPartyBundle/DataFixtures/ORM/LoadThirdParty.php index a1015686f..905461ca5 100644 --- a/src/Bundle/ChillThirdPartyBundle/DataFixtures/ORM/LoadThirdParty.php +++ b/src/Bundle/ChillThirdPartyBundle/DataFixtures/ORM/LoadThirdParty.php @@ -21,6 +21,7 @@ use Doctrine\Bundle\FixturesBundle\Fixture; use Doctrine\Common\DataFixtures\DependentFixtureInterface; use Doctrine\Persistence\ObjectManager; use Iterator; +use libphonenumber\PhoneNumberUtil; use Nelmio\Alice\Loader\NativeLoader; use Nelmio\Alice\ObjectSet; @@ -29,6 +30,13 @@ use function count; class LoadThirdParty extends Fixture implements DependentFixtureInterface { + private PhoneNumberUtil $phoneNumberUtil; + + public function __construct() + { + $this->phoneNumberUtil = PhoneNumberUtil::getInstance(); + } + public function getDependencies() { return [ @@ -66,7 +74,7 @@ class LoadThirdParty extends Fixture implements DependentFixtureInterface Address::class => [ 'address1' => [ 'name' => '', - 'telephone' => '', + 'telephone' => $this->phoneNumberUtil->getExampleNumber('FR'), 'email' => '', 'comment' => '', ], @@ -116,7 +124,7 @@ class LoadThirdParty extends Fixture implements DependentFixtureInterface ThirdParty::class => [ 'thirdparty{1..75}' => [ 'name' => '', - 'telephone' => '', + 'telephone' => $this->phoneNumberUtil->getExampleNumber('FR'), 'email' => '', 'comment' => '', 'address' => '@address', diff --git a/src/Bundle/ChillThirdPartyBundle/Entity/ThirdParty.php b/src/Bundle/ChillThirdPartyBundle/Entity/ThirdParty.php index 25ef1be65..678084045 100644 --- a/src/Bundle/ChillThirdPartyBundle/Entity/ThirdParty.php +++ b/src/Bundle/ChillThirdPartyBundle/Entity/ThirdParty.php @@ -24,6 +24,7 @@ use DateTimeInterface; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; +use libphonenumber\PhoneNumber; use Symfony\Component\Serializer\Annotation\Context; use Symfony\Component\Serializer\Annotation\DiscriminatorMap; use Symfony\Component\Serializer\Annotation\Groups; @@ -253,14 +254,11 @@ class ThirdParty implements TrackCreationInterface, TrackUpdateInterface private ?ThirdPartyProfession $profession = null; /** - * @ORM\Column(name="telephone", type="string", length=64, nullable=true) - * @Assert\Regex("/^([\+{1}])([0-9\s*]{4,20})$/", - * message="Invalid phone number: it should begin with the international prefix starting with ""+"", hold only digits and be smaller than 20 characters. Ex: +33123456789" - * ) + * @ORM\Column(name="telephone", type="phone_number", nullable=true) * @PhonenumberConstraint(type="any") * @Groups({"read", "write", "docgen:read", "docgen:read:3party:parent"}) */ - private ?string $telephone = null; + private ?PhoneNumber $telephone = null; /** * @ORM\Column(name="types", type="json", nullable=true) @@ -502,7 +500,7 @@ class ThirdParty implements TrackCreationInterface, TrackUpdateInterface /** * Get telephone. */ - public function getTelephone(): ?string + public function getTelephone(): ?PhoneNumber { return $this->telephone; } @@ -821,12 +819,8 @@ class ThirdParty implements TrackCreationInterface, TrackUpdateInterface /** * Set telephone. - * - * @param string|null $telephone - * - * @return ThirdParty */ - public function setTelephone($telephone = null) + public function setTelephone(?PhoneNumber $telephone = null): self { $this->telephone = $telephone; diff --git a/src/Bundle/ChillThirdPartyBundle/Form/ThirdPartyType.php b/src/Bundle/ChillThirdPartyBundle/Form/ThirdPartyType.php index 7ca5188b1..480808dfe 100644 --- a/src/Bundle/ChillThirdPartyBundle/Form/ThirdPartyType.php +++ b/src/Bundle/ChillThirdPartyBundle/Form/ThirdPartyType.php @@ -12,6 +12,7 @@ declare(strict_types=1); namespace Chill\ThirdPartyBundle\Form; use Chill\MainBundle\Form\Type\ChillCollectionType; +use Chill\MainBundle\Form\Type\ChillPhoneNumberType; use Chill\MainBundle\Form\Type\ChillTextareaType; use Chill\MainBundle\Form\Type\PickAddressType; use Chill\MainBundle\Form\Type\PickCenterType; @@ -75,7 +76,7 @@ class ThirdPartyType extends AbstractType ->add('name', TextType::class, [ 'required' => true, ]) - ->add('telephone', TextType::class, [ + ->add('telephone', ChillPhoneNumberType::class, [ 'label' => 'Phonenumber', 'required' => false, ]) diff --git a/src/Bundle/ChillThirdPartyBundle/Resources/views/Entity/thirdparty.html.twig b/src/Bundle/ChillThirdPartyBundle/Resources/views/Entity/thirdparty.html.twig index f25f3b263..4c56dfa54 100644 --- a/src/Bundle/ChillThirdPartyBundle/Resources/views/Entity/thirdparty.html.twig +++ b/src/Bundle/ChillThirdPartyBundle/Resources/views/Entity/thirdparty.html.twig @@ -110,8 +110,8 @@ }) }}
  • - {% if thirdparty.telephone %} - {{ thirdparty.telephone|chill_format_phonenumber }} + {% if thirdparty.telephone is not null %} + {{ thirdparty.telephone|chill_format_phonenumber }} {% else %} {{ 'thirdparty.No_phonenumber'|trans }} {% endif %} @@ -136,7 +136,7 @@
  • {% if thirdparty.telephone %} - {{ thirdparty.telephone|chill_format_phonenumber }} + {{ thirdparty.telephone|chill_format_phonenumber }} {% else %} {{ 'thirdparty.No_phonenumber'|trans }} {% endif %} diff --git a/src/Bundle/ChillThirdPartyBundle/Resources/views/ThirdParty/view.html.twig b/src/Bundle/ChillThirdPartyBundle/Resources/views/ThirdParty/view.html.twig index 824438fa3..5a6a75225 100644 --- a/src/Bundle/ChillThirdPartyBundle/Resources/views/ThirdParty/view.html.twig +++ b/src/Bundle/ChillThirdPartyBundle/Resources/views/ThirdParty/view.html.twig @@ -67,10 +67,10 @@
    {{ 'Phonenumber'|trans }}
    {% if thirdParty.telephone == null %} - {{ 'No phone given'|trans }} + {{ 'thirdparty.No_phonenumber'|trans }} {% else %} - - {{ thirdParty.telephone|chill_print_or_message("thirdparty.No_phonenumber") }} + + {{ thirdParty.telephone|chill_format_phonenumber }} {% endif %}
    diff --git a/src/Bundle/ChillThirdPartyBundle/Serializer/Normalizer/ThirdPartyNormalizer.php b/src/Bundle/ChillThirdPartyBundle/Serializer/Normalizer/ThirdPartyNormalizer.php index c107d8485..65bc15ba0 100644 --- a/src/Bundle/ChillThirdPartyBundle/Serializer/Normalizer/ThirdPartyNormalizer.php +++ b/src/Bundle/ChillThirdPartyBundle/Serializer/Normalizer/ThirdPartyNormalizer.php @@ -62,7 +62,7 @@ class ThirdPartyNormalizer implements NormalizerAwareInterface, NormalizerInterf }, $thirdParty->getTypesAndCategories()), 'profession' => $this->normalizer->normalize($thirdParty->getProfession(), $format, $context), 'address' => $this->normalizer->normalize($thirdParty->getAddress(), $format, ['address_rendering' => 'short']), - 'phonenumber' => $thirdParty->getTelephone(), + 'phonenumber' => $this->normalizer->normalize($thirdParty->getTelephone()), 'email' => $thirdParty->getEmail(), 'isChild' => $thirdParty->isChild(), 'parent' => $this->normalizer->normalize($thirdParty->getParent(), $format, $context), diff --git a/src/Bundle/ChillThirdPartyBundle/migrations/Version20220302143821.php b/src/Bundle/ChillThirdPartyBundle/migrations/Version20220302143821.php new file mode 100644 index 000000000..6ffb33472 --- /dev/null +++ b/src/Bundle/ChillThirdPartyBundle/migrations/Version20220302143821.php @@ -0,0 +1,70 @@ +addSql('ALTER TABLE chill_3party.third_party ALTER telephone TYPE VARCHAR(64)'); + $this->addSql('ALTER TABLE chill_3party.third_party ALTER telephone DROP DEFAULT'); + $this->addSql('COMMENT ON COLUMN chill_3party.third_party.telephone IS NULL'); + } + + public function getDescription(): string + { + return 'Upgrade phonenumber on third parties'; + } + + public function up(Schema $schema): void + { + $carrier_code = $this->container + ->getParameter('chill_main')['phone_helper']['default_carrier_code']; + + if (null === $carrier_code) { + throw new RuntimeException('no carrier code'); + } + + $this->addSql('ALTER TABLE chill_3party.third_party ALTER telephone TYPE VARCHAR(35)'); + $this->addSql('ALTER TABLE chill_3party.third_party ALTER telephone DROP DEFAULT'); + $this->addSql('ALTER TABLE chill_3party.third_party ALTER telephone TYPE VARCHAR(35)'); + $this->addSql('COMMENT ON COLUMN chill_3party.third_party.telephone IS \'(DC2Type:phone_number)\''); + + $this->addSql( + 'UPDATE chill_3party.third_party SET ' . + $this->buildMigrationPhonenumberClause($carrier_code, 'telephone') + ); + } + + private function buildMigrationPhoneNumberClause(string $defaultCarriercode, string $field): string + { + $util = PhoneNumberUtil::getInstance(); + + $countryCode = $util->getCountryCodeForRegion($defaultCarriercode); + + return sprintf('%s=CASE + WHEN %s = \'\' THEN NULL + WHEN LEFT(%s, 1) = \'0\' + THEN \'+%s\' || replace(replace(substr(%s, 2), \'(0)\', \'\'), \' \', \'\') + ELSE replace(replace(%s, \'(0)\', \'\'),\' \', \'\') + END', $field, $field, $field, $countryCode, $field, $field); + } +} diff --git a/tests/app b/tests/app index 0fef0f216..3961348aa 160000 --- a/tests/app +++ b/tests/app @@ -1 +1 @@ -Subproject commit 0fef0f21602989ed3aa6b301080ae406d71dd632 +Subproject commit 3961348aa322b98fff625c09d79f8d2f3cd4d6ae