diff --git a/src/Bundle/ChillCalendarBundle/Command/SendTestShortMessageOnCalendarCommand.php b/src/Bundle/ChillCalendarBundle/Command/SendTestShortMessageOnCalendarCommand.php index d49f1615b..9d79f5654 100644 --- a/src/Bundle/ChillCalendarBundle/Command/SendTestShortMessageOnCalendarCommand.php +++ b/src/Bundle/ChillCalendarBundle/Command/SendTestShortMessageOnCalendarCommand.php @@ -1,5 +1,14 @@ phoneNumberUtil = $phoneNumberUtil; $this->phoneNumberHelper = $phoneNumberHelper; $this->messageForCalendarBuilder = $messageForCalendarBuilder; - $this->messageSender = $messageSender; + $this->transporter = $transporter; $this->userRepository = $userRepository; } - public function getName() { return 'chill:calendar:test-send-short-message'; @@ -81,41 +85,41 @@ class SendTestShortMessageOnCalendarCommand extends Command $helper = $this->getHelper('question'); // start date - $question = new Question("When will start the appointment ? (default: \"1 hour\") ", '1 hour'); - $startDate = new \DateTimeImmutable($helper->ask($input, $output, $question)); + $question = new Question('When will start the appointment ? (default: "1 hour") ', '1 hour'); + $startDate = new DateTimeImmutable($helper->ask($input, $output, $question)); if (false === $startDate) { - throw new \UnexpectedValueException("could not create a date with this date and time"); + throw new UnexpectedValueException('could not create a date with this date and time'); } $calendar->setStartDate($startDate); // end date - $question = new Question("How long will last the appointment ? (default: \"PT30M\") ", 'PT30M'); - $interval = new \DateInterval($helper->ask($input, $output, $question)); + $question = new Question('How long will last the appointment ? (default: "PT30M") ', 'PT30M'); + $interval = new DateInterval($helper->ask($input, $output, $question)); if (false === $interval) { - throw new \UnexpectedValueException("could not create the interval"); + throw new UnexpectedValueException('could not create the interval'); } $calendar->setEndDate($calendar->getStartDate()->add($interval)); // a person - $question = new Question("Who will participate ? Give an id for a person. "); + $question = new Question('Who will participate ? Give an id for a person. '); $question ->setValidator(function ($answer): Person { if (!is_numeric($answer)) { - throw new \UnexpectedValueException('the answer must be numeric'); + throw new UnexpectedValueException('the answer must be numeric'); } if (0 >= (int) $answer) { - throw new \UnexpectedValueException('the answer must be greater than zero'); + throw new UnexpectedValueException('the answer must be greater than zero'); } $person = $this->personRepository->find((int) $answer); if (null === $person) { - throw new \UnexpectedValueException("The person is not found"); + throw new UnexpectedValueException('The person is not found'); } return $person; @@ -124,23 +128,22 @@ class SendTestShortMessageOnCalendarCommand extends Command $person = $helper->ask($input, $output, $question); $calendar->addPerson($person); - // a main user - $question = new Question("Who will be the main user ? Give an id for a user. "); + $question = new Question('Who will be the main user ? Give an id for a user. '); $question ->setValidator(function ($answer): User { if (!is_numeric($answer)) { - throw new \UnexpectedValueException('the answer must be numeric'); + throw new UnexpectedValueException('the answer must be numeric'); } if (0 >= (int) $answer) { - throw new \UnexpectedValueException('the answer must be greater than zero'); + throw new UnexpectedValueException('the answer must be greater than zero'); } $user = $this->userRepository->find((int) $answer); if (null === $user) { - throw new \UnexpectedValueException("The user is not found"); + throw new UnexpectedValueException('The user is not found'); } return $user; @@ -150,21 +153,22 @@ class SendTestShortMessageOnCalendarCommand extends Command $calendar->setMainUser($user); // phonenumber - $question = new Question("To which number are we going to send this fake message ?", - null !== $person->getMobilenumber() ? - $this->phoneNumberHelper->format($person->getMobilenumber()): - null + $phonenumberFormatted = null !== $person->getMobilenumber() ? + $this->phoneNumberUtil->format($person->getMobilenumber(), PhoneNumberFormat::E164) : ''; + $question = new Question( + sprintf('To which number are we going to send this fake message ? (default to: %s)', $phonenumberFormatted), + $phonenumberFormatted ); $question->setNormalizer(function ($answer): PhoneNumber { if (null === $answer) { - throw new \UnexpectedValueException("The person is not found"); + throw new UnexpectedValueException('The person is not found'); } $phone = $this->phoneNumberUtil->parse($answer, 'BE'); if (!$this->phoneNumberUtil->isPossibleNumberForType($phone, PhoneNumberType::MOBILE)) { - throw new \UnexpectedValueException("Phone number si not a mobile"); + throw new UnexpectedValueException('Phone number si not a mobile'); } return $phone; @@ -172,6 +176,9 @@ class SendTestShortMessageOnCalendarCommand extends Command $phone = $helper->ask($input, $output, $question); + $question = new ConfirmationQuestion('really send the message to the phone ?'); + $reallySend = (bool) $helper->ask($input, $output, $question); + $messages = $this->messageForCalendarBuilder->buildMessageForCalendar($calendar); if (0 === count($messages)) { @@ -179,10 +186,13 @@ class SendTestShortMessageOnCalendarCommand extends Command } foreach ($messages as $key => $message) { - $output->writeln("The short message for SMS ${key} will be: "); + $output->writeln("The short message for SMS {$key} will be: "); $output->writeln($message->getContent()); $message->setPhoneNumber($phone); - $this->messageSender->send($message); + + if ($reallySend) { + $this->transporter->send($message); + } } return 0; diff --git a/src/Bundle/ChillCalendarBundle/DependencyInjection/Configuration.php b/src/Bundle/ChillCalendarBundle/DependencyInjection/Configuration.php index 8f8a18b97..0d295ccbb 100644 --- a/src/Bundle/ChillCalendarBundle/DependencyInjection/Configuration.php +++ b/src/Bundle/ChillCalendarBundle/DependencyInjection/Configuration.php @@ -30,7 +30,7 @@ class Configuration implements ConfigurationInterface ->children() ->arrayNode('short_messages') ->canBeDisabled() - ->children()->end() + ->children()->end() ->end() // end for short_messages ->arrayNode('remote_calendars_sync')->canBeEnabled() ->children() diff --git a/src/Bundle/ChillCalendarBundle/Resources/views/CalendarShortMessage/short_message.txt.twig b/src/Bundle/ChillCalendarBundle/Resources/views/CalendarShortMessage/short_message.txt.twig index 1e5a0794b..d6c46a7e9 100644 --- a/src/Bundle/ChillCalendarBundle/Resources/views/CalendarShortMessage/short_message.txt.twig +++ b/src/Bundle/ChillCalendarBundle/Resources/views/CalendarShortMessage/short_message.txt.twig @@ -1 +1 @@ -Votre travailleur social {{ calendar.mainUser.label }} vous rencontrera le {{ calendar.startDate|format_date('short') }} à {{ calendar.startDate|format_time('medium') }} - LIEU. {% if calendar.location is not null and calendar.location.phonenumber is not null %}En cas d’indisponibilité rappelez-nous au {{ calendar.location.phonenumber|chill_format_phonenumber }}.{% endif %} +Votre travailleur social {{ calendar.mainUser.label }} vous rencontrera le {{ calendar.startDate|format_date('short', locale='fr') }} à {{ calendar.startDate|format_time('short', locale='fr') }} - LIEU.{% if calendar.mainUser.mainLocation is not null and calendar.mainUser.mainLocation.phonenumber1 is not null %} En cas d'indisponibilité, appelez-nous au {{ calendar.mainUser.mainLocation.phonenumber1|chill_format_phonenumber }}.{% endif %} diff --git a/src/Bundle/ChillCalendarBundle/Service/ShortMessageNotification/DefaultRangeGenerator.php b/src/Bundle/ChillCalendarBundle/Service/ShortMessageNotification/DefaultRangeGenerator.php new file mode 100644 index 000000000..0587296a5 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Service/ShortMessageNotification/DefaultRangeGenerator.php @@ -0,0 +1,68 @@ + Envoi des rdv du mardi et mercredi. + * * Mardi => Envoi des rdv du jeudi. + * * Mercredi => Envoi des rdv du vendredi + * * Jeudi => envoi des rdv du samedi et dimanche + * * Vendredi => Envoi des rdv du lundi. + */ +class DefaultRangeGenerator implements RangeGeneratorInterface +{ + public function generateRange(\DateTimeImmutable $date): array + { + $onMidnight = DateTimeImmutable::createFromFormat('Y-m-d H:i:s', $date->format('Y-m-d') . ' 00:00:00'); + + switch ($dow = (int) $onMidnight->format('w')) { + case 6: // Saturday + case 0: // Sunday + return ['startDate' => null, 'endDate' => null]; + + case 1: // Monday + // send for Tuesday and Wednesday + $startDate = $onMidnight->add(new DateInterval('P1D')); + $endDate = $startDate->add(new DateInterval('P2D')); + + break; + + case 2: // tuesday + case 3: // wednesday + $startDate = $onMidnight->add(new DateInterval('P2D')); + $endDate = $startDate->add(new DateInterval('P1D')); + + break; + + case 4: // thursday + $startDate = $onMidnight->add(new DateInterval('P2D')); + $endDate = $startDate->add(new DateInterval('P2D')); + + break; + + case 5: // friday + $startDate = $onMidnight->add(new DateInterval('P3D')); + $endDate = $startDate->add(new DateInterval('P1D')); + + break; + + default: + throw new UnexpectedValueException('a day of a week should not have the value: ' . $dow); + } + + return ['startDate' => $startDate, 'endDate' => $endDate]; + } +} diff --git a/src/Bundle/ChillCalendarBundle/Service/ShortMessageNotification/DefaultShortMessageForCalendarBuilder.php b/src/Bundle/ChillCalendarBundle/Service/ShortMessageNotification/DefaultShortMessageForCalendarBuilder.php index 71c51640e..802605179 100644 --- a/src/Bundle/ChillCalendarBundle/Service/ShortMessageNotification/DefaultShortMessageForCalendarBuilder.php +++ b/src/Bundle/ChillCalendarBundle/Service/ShortMessageNotification/DefaultShortMessageForCalendarBuilder.php @@ -1,10 +1,17 @@ engine->render('@ChillCalendar/CalendarShortMessage/short_message.txt.twig', ['calendar' => $calendar]), - $person->getMobilenumber() + $this->engine->render('@ChillCalendar/CalendarShortMessage/short_message.txt.twig', ['calendar' => $calendar]), + $person->getMobilenumber() ); } diff --git a/src/Bundle/ChillCalendarBundle/Service/ShortMessageNotification/Generator.php b/src/Bundle/ChillCalendarBundle/Service/ShortMessageNotification/Generator.php new file mode 100644 index 000000000..d78faff4c --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Service/ShortMessageNotification/Generator.php @@ -0,0 +1,28 @@ + + */ + public function generateRange(DateTimeImmutable $date): array; +} diff --git a/src/Bundle/ChillCalendarBundle/Service/ShortMessageNotification/ShortMessageForCalendarBuilderInterface.php b/src/Bundle/ChillCalendarBundle/Service/ShortMessageNotification/ShortMessageForCalendarBuilderInterface.php index 327bb89b0..ffcc8fe5c 100644 --- a/src/Bundle/ChillCalendarBundle/Service/ShortMessageNotification/ShortMessageForCalendarBuilderInterface.php +++ b/src/Bundle/ChillCalendarBundle/Service/ShortMessageNotification/ShortMessageForCalendarBuilderInterface.php @@ -1,14 +1,22 @@ Envoi des rdv du mardi et mercredi. + * * Mardi => Envoi des rdv du jeudi. + * * Mercredi => Envoi des rdv du vendredi + * * Jeudi => envoi des rdv du samedi et dimanche + * * Vendredi => Envoi des rdv du lundi. + */ + public function generateData(): Iterator + { + yield [ + new DateTimeImmutable('2022-06-13 10:45:00'), + new DateTimeImmutable('2022-06-14 00:00:00'), + new DateTimeImmutable('2022-06-16 00:00:00'), + ]; + + yield [ + new DateTimeImmutable('2022-06-14 15:45:00'), + new DateTimeImmutable('2022-06-16 00:00:00'), + new DateTimeImmutable('2022-06-17 00:00:00'), + ]; + + yield [ + new DateTimeImmutable('2022-06-15 13:45:18'), + new DateTimeImmutable('2022-06-17 00:00:00'), + new DateTimeImmutable('2022-06-18 00:00:00'), + ]; + + yield [ + new DateTimeImmutable('2022-06-16 01:30:55'), + new DateTimeImmutable('2022-06-18 00:00:00'), + new DateTimeImmutable('2022-06-20 00:00:00'), + ]; + + yield [ + new DateTimeImmutable('2022-06-17 21:30:55'), + new DateTimeImmutable('2022-06-20 00:00:00'), + new DateTimeImmutable('2022-06-21 00:00:00'), + ]; + + yield [ + new DateTimeImmutable('2022-06-18 21:30:55'), + null, + null, + ]; + + yield [ + new DateTimeImmutable('2022-06-19 21:30:55'), + null, + null, + ]; + } + + /** + * @dataProvider generateData + */ + public function testGenerateRange(DateTimeImmutable $date, ?DateTimeImmutable $startDate, ?DateTimeImmutable $endDate) + { + $generator = new DefaultRangeGenerator(); + + ['startDate' => $actualStartDate, 'endDate' => $actualEndDate] = $generator->generateRange($date); + + $this->assertEquals($startDate->format(DateTimeImmutable::ATOM), $actualStartDate->format(DateTimeImmutable::ATOM)); + $this->assertEquals($endDate->format(DateTimeImmutable::ATOM), $actualEndDate->format(DateTimeImmutable::ATOM)); + } +} diff --git a/src/Bundle/ChillMainBundle/ChillMainBundle.php b/src/Bundle/ChillMainBundle/ChillMainBundle.php index 32e075b7f..54da6911a 100644 --- a/src/Bundle/ChillMainBundle/ChillMainBundle.php +++ b/src/Bundle/ChillMainBundle/ChillMainBundle.php @@ -18,6 +18,7 @@ use Chill\MainBundle\DependencyInjection\CompilerPass\GroupingCenterCompilerPass use Chill\MainBundle\DependencyInjection\CompilerPass\MenuCompilerPass; use Chill\MainBundle\DependencyInjection\CompilerPass\NotificationCounterCompilerPass; use Chill\MainBundle\DependencyInjection\CompilerPass\SearchableServicesCompilerPass; +use Chill\MainBundle\DependencyInjection\CompilerPass\ShortMessageCompilerPass; use Chill\MainBundle\DependencyInjection\CompilerPass\TimelineCompilerClass; use Chill\MainBundle\DependencyInjection\CompilerPass\WidgetsCompilerPass; use Chill\MainBundle\DependencyInjection\ConfigConsistencyCompilerPass; @@ -70,5 +71,6 @@ class ChillMainBundle extends Bundle $container->addCompilerPass(new ACLFlagsCompilerPass()); $container->addCompilerPass(new GroupingCenterCompilerPass()); $container->addCompilerPass(new CRUDControllerCompilerPass()); + $container->addCompilerPass(new ShortMessageCompilerPass()); } } diff --git a/src/Bundle/ChillMainBundle/DependencyInjection/ChillMainExtension.php b/src/Bundle/ChillMainBundle/DependencyInjection/ChillMainExtension.php index e1a7d2544..a435bbe5d 100644 --- a/src/Bundle/ChillMainBundle/DependencyInjection/ChillMainExtension.php +++ b/src/Bundle/ChillMainBundle/DependencyInjection/ChillMainExtension.php @@ -199,6 +199,8 @@ class ChillMainExtension extends Extension implements $loader->load('services/short_message.yaml'); $this->configureCruds($container, $config['cruds'], $config['apis'], $loader); + $container->setParameter('chill_main.short_messages', $config['short_messages']); + //$this->configureSms($config['short_messages'], $container, $loader); } public function prepend(ContainerBuilder $container) @@ -314,6 +316,13 @@ class ChillMainExtension extends Extension implements // Note: the controller are loaded inside compiler pass } + protected function configureSms(array $config, ContainerBuilder $container, Loader\YamlFileLoader $loader): void + { + $dsn = parse_url($config['dsn']); + + dump($dsn); + } + protected function prependCruds(ContainerBuilder $container) { $container->prependExtensionConfig('chill_main', [ diff --git a/src/Bundle/ChillMainBundle/DependencyInjection/CompilerPass/ShortMessageCompilerPass.php b/src/Bundle/ChillMainBundle/DependencyInjection/CompilerPass/ShortMessageCompilerPass.php new file mode 100644 index 000000000..fa9994408 --- /dev/null +++ b/src/Bundle/ChillMainBundle/DependencyInjection/CompilerPass/ShortMessageCompilerPass.php @@ -0,0 +1,87 @@ +resolveEnvPlaceholders($container->getParameter('chill_main.short_messages', null), true); + // weird fix for special characters + $config['dsn'] = str_replace(['%%'], ['%'], $config['dsn']); + $dsn = parse_url($config['dsn']); + parse_str($dsn['query'] ?? '', $dsn['queries']); + + if ('null' === $dsn['scheme'] || false === $config['enabled']) { + $defaultTransporter = new Reference(NullShortMessageSender::class); + } elseif ('ovh' === $dsn['scheme']) { + if (!class_exists('\Ovh\Api')) { + throw new RuntimeException('Class \\Ovh\\Api not found'); + } + + foreach (['user', 'host', 'pass'] as $component) { + if (!array_key_exists($component, $dsn)) { + throw new RuntimeException(sprintf('The component %s does not exist in dsn. Please provide a dsn ' . + 'like ovh://applicationKey:applicationSecret@endpoint?consumerKey=xxxx&sender=yyyy&service_name=zzzz', $component)); + } + + $container->setParameter('chill_main.short_messages.ovh_config_' . $component, $dsn[$component]); + } + + foreach (['consumer_key', 'sender', 'service_name'] as $param) { + if (!array_key_exists($param, $dsn['queries'])) { + throw new RuntimeException(sprintf('The parameter %s does not exist in dsn. Please provide a dsn ' . + 'like ovh://applicationKey:applicationSecret@endpoint?consumerKey=xxxx&sender=yyyy&service_name=zzzz', $param)); + } + $container->setParameter('chill_main.short_messages.ovh_config_' . $param, $dsn['queries'][$param]); + } + + $ovh = new Definition(); + $ovh + ->setClass('\Ovh\Api') + ->setArgument(0, $dsn['user']) + ->setArgument(1, $dsn['pass']) + ->setArgument(2, $dsn['host']) + ->setArgument(3, $dsn['queries']['consumer_key']); + $container->setDefinition('Ovh\Api', $ovh); + + $ovhSender = new Definition(); + $ovhSender + ->setClass(OvhShortMessageSender::class) + ->setArgument(0, new Reference('Ovh\Api')) + ->setArgument(1, $dsn['queries']['service_name']) + ->setArgument(2, $dsn['queries']['sender']) + ->setArgument(3, new Reference(LoggerInterface::class)) + ->setArgument(4, new Reference(PhoneNumberUtil::class)); + $container->setDefinition(OvhShortMessageSender::class, $ovhSender); + + $defaultTransporter = new Reference(OvhShortMessageSender::class); + } else { + throw new RuntimeException(sprintf('Cannot find a sender for this dsn: %s', $config['dsn'])); + } + + $container->getDefinition(ShortMessageTransporter::class) + ->setArgument(0, $defaultTransporter); + } +} diff --git a/src/Bundle/ChillMainBundle/DependencyInjection/Configuration.php b/src/Bundle/ChillMainBundle/DependencyInjection/Configuration.php index a9b3ffec0..19b43de43 100644 --- a/src/Bundle/ChillMainBundle/DependencyInjection/Configuration.php +++ b/src/Bundle/ChillMainBundle/DependencyInjection/Configuration.php @@ -102,6 +102,14 @@ class Configuration implements ConfigurationInterface ->end() ->end() ->end() + ->arrayNode('short_messages') + ->canBeEnabled() + ->children() + ->scalarNode('dsn')->cannotBeEmpty()->defaultValue('null://null') + ->info('the dsn for sending short message. Example: ovh://applicationKey:secret@endpoint') + ->end() + ->end() + ->end() // end for 'short_messages' ->arrayNode('acl') ->addDefaultsIfNotSet() ->children() diff --git a/src/Bundle/ChillMainBundle/Service/ShortMessage/NullShortMessageSender.php b/src/Bundle/ChillMainBundle/Service/ShortMessage/NullShortMessageSender.php index 9e52766e3..2d5e3e7e2 100644 --- a/src/Bundle/ChillMainBundle/Service/ShortMessage/NullShortMessageSender.php +++ b/src/Bundle/ChillMainBundle/Service/ShortMessage/NullShortMessageSender.php @@ -1,5 +1,14 @@ content = $content; $this->phoneNumber = $phoneNumber; + $this->priority = $priority; } public function getContent(): string @@ -35,21 +38,29 @@ class ShortMessage return $this->phoneNumber; } - /** - * @param string $content - */ - public function setContent(string $content): void + public function getPriority(): string + { + return $this->priority; + } + + public function setContent(string $content): self { $this->content = $content; + + return $this; } - /** - * @param PhoneNumber $phoneNumber - */ - public function setPhoneNumber(PhoneNumber $phoneNumber): void + public function setPhoneNumber(PhoneNumber $phoneNumber): self { $this->phoneNumber = $phoneNumber; + + return $this; } + public function setPriority(string $priority): self + { + $this->priority = $priority; + return $this; + } } diff --git a/src/Bundle/ChillMainBundle/Service/ShortMessage/ShortMessageSenderInterface.php b/src/Bundle/ChillMainBundle/Service/ShortMessage/ShortMessageSenderInterface.php index 91d534a83..fb52aac23 100644 --- a/src/Bundle/ChillMainBundle/Service/ShortMessage/ShortMessageSenderInterface.php +++ b/src/Bundle/ChillMainBundle/Service/ShortMessage/ShortMessageSenderInterface.php @@ -1,5 +1,14 @@ sender = $sender; + } + + public function send(ShortMessage $shortMessage): void + { + $this->sender->send($shortMessage); + } +} diff --git a/src/Bundle/ChillMainBundle/Service/ShortMessage/ShortMessageTransporterInterface.php b/src/Bundle/ChillMainBundle/Service/ShortMessage/ShortMessageTransporterInterface.php new file mode 100644 index 000000000..42142c7eb --- /dev/null +++ b/src/Bundle/ChillMainBundle/Service/ShortMessage/ShortMessageTransporterInterface.php @@ -0,0 +1,17 @@ +api = $api; + $this->serviceName = $serviceName; + $this->sender = $sender; + $this->logger = $logger; + $this->phoneNumberUtil = $phoneNumberUtil; + } + + public function send(ShortMessage $shortMessage): void + { + $receiver = $this->phoneNumberUtil->format($shortMessage->getPhoneNumber(), PhoneNumberFormat::E164); + + $response = $this->api->post( + strtr('/sms/{serviceName}/jobs', ['{serviceName}' => $this->serviceName]), + [ + 'message' => $shortMessage->getContent(), + 'receivers' => [$receiver], + 'sender' => $this->sender, + 'noStopClause' => true, + 'coding' => '7bit', + 'charset' => 'UTF-8', + 'priority' => $shortMessage->getPriority(), + ] + ); + + $improved = array_merge([ + 'validReceiversI' => implode(',', $response['validReceivers']), + 'idsI' => implode(',', $response['ids']), + ], $response); + + $this->logger->warning('[sms] a sms was sent', $improved); + } +}