src/Services/Communication/SmsTools.php line 61

Open in your IDE?
  1. <?php
  2. //----------------------------------------------------------------------
  3. // src/Services/Communication/SmsTools.php
  4. //----------------------------------------------------------------------
  5. namespace App\Services\Communication;
  6. use Doctrine\Persistence\ManagerRegistry;
  7. use Symfony\Component\Routing\RouterInterface;
  8. use Symfony\Contracts\Translation\TranslatorInterface;
  9. use App\Entity\Access;
  10. use App\Entity\SocietyGroup;
  11. use App\Entity\Client\Client;
  12. use App\Entity\Client\Individual;
  13. use App\Entity\Communication\Sms\Sms;
  14. use App\Entity\Communication\Sms\SmsStatus;
  15. use App\Entity\Communication\Sms\SmsMonthlyCostSociety;
  16. use App\Entity\Communication\Sms\SmsMonthlyCostSocietyGroup;
  17. use App\Entity\Communication\Sms\SmsPhoneNumber;
  18. use App\Entity\Communication\Sms\SmsPrice;
  19. use App\Entity\Communication\SmsProviders\SmsMode;
  20. use App\Entity\Communication\SmsProviders\SmsProvider;
  21. use App\Entity\Communication\SmsProviders\SmsTwilio;
  22. use App\Entity\Config\Config;
  23. use App\Entity\Config\OptionConfig;
  24. use App\Entity\HR\HumanResource;
  25. use App\Entity\Mission\Mission;
  26. use App\Entity\Planning\Task;
  27. use App\Entity\Platform\Devis\Devis;
  28. use App\Entity\Platform\Invoice\Invoice;
  29. use App\Entity\Platform\Phone;
  30. use App\Entity\Platform\Society;
  31. use App\Services\Communication\SmsProviderTools;
  32. use App\Services\LogTools;
  33. use App\Services\Platform\KeywordTools;
  34. use App\Services\Security\PasswordTools;
  35. /**
  36.  * SmsTools
  37.  *
  38.  * Naming conventions :
  39.  *         $smsProvider can be an object of type
  40.  *            SmsProvider::SmsMode
  41.  *            SmsProvider::SmsTwilio
  42.  *        $sms is an object of type Sms::Sms
  43.  */
  44. class SmsTools
  45. {
  46.     const COEFFICIENT_PRICE_RESPONSE 10.00;
  47.     public function __construct(
  48.         $devRealm$testRealm$prodRealm,
  49.         ManagerRegistry $doctrineLogTools $logToolsPasswordTools $passwordToolsRouterInterface $routerTranslatorInterface $translator,
  50.         KeywordTools $keywordToolsSmsProviderTools $smsProviderTools
  51.     )
  52.     {
  53.         $this->devRealm $devRealm;
  54.         $this->testRealm $testRealm;
  55.         $this->prodRealm $prodRealm;
  56.         $this->em $doctrine->getManager();
  57.         $this->logTools $logTools;
  58.         $this->passwordTools $passwordTools;
  59.         $this->router $router;
  60.         $this->translator $translator;
  61.         $this->keywordTools $keywordTools;
  62.         $this->smsProviderTools $smsProviderTools;
  63.         $this->debug true;
  64.     }
  65.     /**
  66.      * Get the currently active SmsProvider
  67.      * Note : Only one SmsProvider should be active
  68.      */
  69.     public function getActiveSmsProvider()
  70.     {
  71.         return $this->em->getRepository(SmsProvider::class)->findOneBy(array(
  72.             'isActive'        =>    1,
  73.         ));
  74.     }
  75.     /**
  76.      * Calls SmsProviderTools :: craftSmsProvider to create and SmsProvider
  77.      * Calls SmsProviderTools :: sendSms to send the sms/smsProvider
  78.      */
  79.     public function sendSms(Sms $sms)
  80.     {
  81.         $this->logTools->sms_debug($this->debug"[SmsTools :: sendSms] Calling SmsProviderTools :: craftSmsProvider to create a SmsProvider (SmsMode or SmsTwilio object) for the given Sms");
  82.         $smsProvider $this->smsProviderTools->craftSmsProvider($sms);
  83.         $this->logTools->sms_debug($this->debug"[SmsTools :: sendSms] Calling SmsProviderTools :: sendSms");
  84.         return $this->smsProviderTools->sendSms($smsProvider);
  85.     }
  86.     /**
  87.      * Plan.io Task #4515
  88.      * Init sms for Form
  89.      * Recipient is not set here but in SmsTools :: handleSmsPostFormSubmit
  90.      */
  91.     public function initSmsForClient(Client $clientAccess $accessSocietyGroup $societyGroup$mission null)
  92.     {
  93.         $activeSmsProvider $this->em->getRepository(SmsProvider::class)->findOneBy(array(
  94.             'isActive'        =>    1,
  95.         ));
  96.         // Only for individuals for now
  97.         $individual $client->getIndividual();
  98.         if ($individual === null)
  99.         {
  100.             $msg "Individual is null for client ".$client->displayForLog();
  101.             $this->logTools->errorlog($msg);
  102.             $this->logTools->smsLogDebug($msg$activeSmsProvider);
  103.             return null;
  104.         }
  105.         $emitterNumber $this->getPhoneNumberForSocietyGroup($societyGroup);
  106.         if (empty($emitterNumber))
  107.         {
  108.             // Errors have been logged in SmsTools::getPhoneNumberForSocietyGroup
  109.             return null;
  110.         }
  111.         $status $this->smsProviderTools->getDefaultSmsStatus();
  112.         if ($status === null)
  113.         {
  114.             // Error has been logged in SmsProviderTools::getDefaultSmsStatus
  115.             return null;
  116.         }
  117.         $sms = new Sms();
  118.         $sms->setProvider($activeSmsProvider);
  119.         $sms->setClient($client);
  120.         $sms->setAuthor($access);
  121.         $sms->setEmitter($emitterNumber);
  122.         $sms->setStatus($status);
  123.         $sms->setSocietyGroup($societyGroup);
  124.         // Plan.io Task #4313 - Allow 'shared client' SMS
  125.         $societySms $individual->getSociety();
  126.         if ($mission !== null)
  127.         {
  128.             $sms->setMission($mission);
  129.             if ($mission->isSharedWithSocietyGroup($societyGroup))
  130.             {
  131.                 $societySms $mission->getSocietyOwner();
  132.             }
  133.         }
  134.         $sms->setSociety($societySms);
  135.         // CraftClient response code if needed
  136.         $this->craftClientResponseCode($sms);
  137.         $this->logTools->sms_debug($this->debug"[SmsTools :: initSmsForClient] New Sms created with status ".$status->getValue());
  138.         return $sms;
  139.     }
  140.     /**
  141.      * Plan.io Task #4515
  142.      * Will sync SMS : Recipient, Reply url
  143.      * Redirect to Config phone if not prod realm
  144.      */
  145.     public function handleSmsPostFormSubmit(Sms $sms)
  146.     {
  147.         $client $sms->getClient();
  148.         if ($client === null)
  149.         {
  150.             // Exit
  151.             $errorMessage "[SmsTools :: handleSmsPostFormSubmit] No client in SMS object";
  152.             $this->logTools->smsLogDebug($errorMessage$sms->getProvider());
  153.             return null;
  154.         }
  155.         $phoneObjectToAttribute $sms->getPhoneTmp();
  156.         if ($phoneObjectToAttribute === null)
  157.         {
  158.             // Exit
  159.             $errorMessage "[SmsTools :: handleSmsPostFormSubmit] No Phone selected by the User in SMS form";
  160.             $this->logTools->smsLogDebug($errorMessage$sms->getProvider());
  161.             return null;
  162.         }
  163.         $this->logTools->sms_debug($this->debug"[SmsTools :: handleSmsPostFormSubmit] Phone selected by the User : " $phoneObjectToAttribute->getNumberFormatted());
  164.         // Determine the good recipient
  165.         if ($this->prodRealm)    $sms->setRecipient($phoneObjectToAttribute->getNumberFormatted());
  166.         else                     $sms->setRecipient($this->getPhoneToRedirect());
  167.         if (empty($sms->getRecipient()))
  168.         {
  169.             // Exit
  170.             $errorMessage "[SmsTools :: handleSmsPostFormSubmit] No valid phone";
  171.             $this->logTools->smsLogDebug($errorMessage$sms->getProvider());
  172.             return null;
  173.         }
  174.         // Update body
  175.         if ($sms->getIncludeReplyCode())
  176.         {
  177.             $sms->setBody($sms->getBody().$sms->getCodeInfo());
  178.         }
  179.         $this->em->persist($sms);
  180.         $this->logTools->sms_debug($this->debug"[SmsTools :: handleSmsPostFormSubmit] Sms with recipient ".$sms->getRecipient());
  181.         return $sms;
  182.     }
  183.     /**
  184.      * Create a Sms object for Task reminder
  185.      * 
  186.      * @usedBy Command::Cron ::SendSmsReminderTaskCommand
  187.      */
  188.     public function createSmsClientReminderTask(Client $clientAccess $accessSocietyGroup $societyGroup$mission null)
  189.     {
  190.         $activeSmsProvider $this->em->getRepository(SmsProvider::class)->findOneBy(array(
  191.             'isActive'        =>    1,
  192.         ));
  193.         // Only for individuals for now
  194.         $individual $client->getIndividual();
  195.         if ($individual === null)
  196.         {
  197.             $msg "Individual is null for client ".$client->displayForLog();
  198.             $this->logTools->errorlog($msg);
  199.             $this->logTools->smsLogDebug($msg$activeSmsProvider);
  200.             return null;
  201.         }
  202.         // Determine the good recipient
  203.         if ($this->prodRealm)    $phone $this->phoneFormat($client->getPhone());
  204.         else                     $phone $this->getPhoneToRedirect();        
  205.         if (empty($phone))
  206.         {
  207.             $errorMessage "[SmsTools :: createSmsClientReminderTask] No valid phone";
  208.             $this->logTools->smsLogDebug($errorMessage$activeSmsProvider);
  209.             return null;
  210.         }
  211.         $emitterNumber $this->getPhoneNumberForSocietyGroup($societyGroup);
  212.         if (empty($emitterNumber))
  213.         {
  214.             // Errors have been logged in SmsTools::getPhoneNumberForSocietyGroup
  215.             return null;
  216.         }
  217.         $status $this->smsProviderTools->getDefaultSmsStatus();
  218.         if ($status === null)
  219.         {
  220.             // Error has been logged in SmsProviderTools::getDefaultSmsStatus
  221.             return null;
  222.         }
  223.         $sms = new Sms();
  224.         $sms->setProvider($activeSmsProvider);
  225.         $sms->setClient($client);
  226.         $sms->setAuthor($access);
  227.         $sms->setEmitter($emitterNumber);
  228.         $sms->setRecipient($phone);
  229.         $sms->setStatus($status);
  230.         $sms->setSocietyGroup($societyGroup);
  231.         // Plan.io Task #4313 - Allow 'shared client' SMS
  232.         $societySms $individual->getSociety();
  233.         if ($mission !== null)
  234.         {
  235.             $sms->setMission($mission);
  236.             if ($mission->isSharedWithSocietyGroup($societyGroup))
  237.             {
  238.                 $societySms $mission->getSocietyOwner();
  239.             }
  240.         }
  241.         $sms->setSociety($societySms);
  242.         $this->em->persist($sms);
  243.         $this->logTools->sms_debug($this->debug"[SmsTools :: createSmsForClientReminder] New Sms created with status ".$status->getValue());
  244.         return $sms;
  245.     }
  246.     /**
  247.      * Creates a Sms object for Human resource
  248.      */
  249.     public function createSmsForHumanResource(HumanResource $humanResourceAccess $accessSocietyGroup $societyGroup)
  250.     {
  251.         $activeSmsProvider $this->em->getRepository(SmsProvider::class)->findOneBy(array(
  252.             'isActive'        =>    1,
  253.         ));
  254.         // Determine the good recipient
  255.         if ($this->prodRealm)    $phone $this->phoneFormat($humanResource->getPhone());
  256.         else                     $phone $this->getPhoneToRedirect();
  257.         if (empty($phone))
  258.         {
  259.             $errorMessage "[SmsTools :: createSmsForHumanResource] No valid phone";
  260.             $this->logTools->smsLogDebug($errorMessage$activeSmsProvider);
  261.             return null;
  262.         }
  263.         $emitterNumber $this->getPhoneNumberForSocietyGroup($societyGroup);
  264.         if (empty($emitterNumber))
  265.         {
  266.             // Errors have been logged in SmsTools::getPhoneNumberForSocietyGroup
  267.             return null;
  268.         }
  269.         $status $this->smsProviderTools->getDefaultSmsStatus();
  270.         if ($status === null)
  271.         {
  272.             // Error has been logged in SmsProviderTools::getDefaultSmsStatus
  273.             return null;
  274.         }
  275.         $sms = new Sms();
  276.         $sms->setProvider($activeSmsProvider);
  277.         $sms->setHumanResource($humanResource);
  278.         $sms->setAuthor($access);
  279.         $sms->setEmitter($emitterNumber);
  280.         $sms->setRecipient($phone);
  281.         $sms->setStatus($status);
  282.         $sms->setSociety($humanResource->getSociety());
  283.         $sms->setSocietyGroup($societyGroup);
  284.         $this->em->persist($sms);
  285.         $this->logTools->sms_debug($this->debug"[SmsTools :: createSmsForHumanResource] New Sms created with status ".$status->getValue());
  286.         return $sms;
  287.     }
  288.     /**
  289.      * Prices are decreasing, total amount of Sms (for a SocietyGroup is considered)
  290.      * Count the current number of actual sms that have been sent for the given Sms object
  291.      * Computes the total price of the given Sms
  292.      * Saves to the Sms object
  293.      */
  294.     public function handlePrice(Sms $smsbool $isResponse false)
  295.     {
  296.         if ($sms->getPrice() !== null)
  297.         {
  298.             return false;
  299.         }
  300.         $society $sms->getSociety();
  301.         if ($society === null)
  302.         {
  303.             return false;
  304.         }
  305.         $societyGroup $society->getGroup();
  306.         if ($societyGroup === null)
  307.         {
  308.             return false;
  309.         }
  310.         $totalSegments 0;
  311.         $price 0.00;
  312.         // Get number of segments for the given sms
  313.         $smsSegmentNumber $sms->getNumberSegment() != null $sms->getNumberSegment() : 1;
  314.         // Get all price ranges
  315.         $stepPrices $this->em
  316.             ->getRepository(SmsPrice::class)
  317.             ->findBy(
  318.                 array(),
  319.                 array('floorLimit'=>'asc')
  320.             );
  321.         // Get the total number of sms (segments) for the given SocietyGroup
  322.         // No time limit
  323.         $totalSegments $this->em->getRepository(Sms::class)->getTotalSmsSegments($societyGroup);
  324.         if ($isResponse)
  325.         {
  326.             // Callback for SMS received => The Sms object has not been written to the DataBase
  327.             // So the $totalSegments does not contains the $smsSegmentNumber
  328.         }
  329.         else
  330.         {
  331.             // Callback for SMS sent => The Sms object already exists in the DataBase
  332.             // So the $totalSegments contains the $smsSegmentNumber
  333.             $totalSegments $totalSegments $smsSegmentNumber;
  334.         }
  335.         // Add cost for the current sms
  336.         // Take into account the fact that for a single sms with multiple segments
  337.         // we can change price range
  338.         for ($iSeg 0$iSeg $smsSegmentNumber$iSeg++)
  339.         {
  340.             $totalSegments ++;
  341.             $priceStep $this->computePriceStep($stepPrices$totalSegments);
  342.             $price $price $priceStep;
  343.         }
  344.         // Handle price when it's a SMS Response
  345.         if ($isResponse)
  346.         {
  347.             $price $price self::COEFFICIENT_PRICE_RESPONSE;
  348.         }
  349.         $sms->setPrice($price);
  350.         // Montly cost call
  351.         $this->monthlyCostUpdateSociety($price$society$societyGroup);
  352.         $this->monthlyCostUpdateSocietyGroup($price$societyGroup);
  353.         return true;
  354.     }
  355.     /**
  356.      * Computes the total price for the given number of segments
  357.      * taking into account the step prices
  358.      */
  359.     public function computePriceStep($stepPrices$nbSegments)
  360.     {
  361.         $counter 0;
  362.         $previousStepIndex 0;
  363.         $price 0;
  364.         foreach ($stepPrices as $s)
  365.         {
  366.             if ($nbSegments >= $s->getFloorLimit())
  367.             {
  368.                 if ($nbSegments <= $s->getCeilLimit())
  369.                 {
  370.                     $price $s->getPrice();
  371.                 }
  372.                 else
  373.                 {
  374.                     $previousStepIndex $counter;
  375.                 }
  376.             }
  377.             $counter++;
  378.         }
  379.         // Cover eventual error in the price table
  380.         if ($price === 0)
  381.         {
  382.             if (array_key_exists($previousStepIndex+1$stepPrices))
  383.             {
  384.                 $price $stepPrices[$previousStepIndex+1]->getPrice();
  385.             }
  386.             else
  387.             {
  388.                 // Task plan.io #3066
  389.                 if (array_key_exists($previousStepIndex$stepPrices))
  390.                 {
  391.                     $price $stepPrices[$previousStepIndex]->getPrice();
  392.                 }
  393.                 else
  394.                 {
  395.                     // Should not happen but here $price = 0;
  396.                     // Security
  397.                     $price 0;
  398.                 }
  399.             }
  400.         }
  401.         return $price;
  402.     }
  403.     /**
  404.      * Plan.io Task #4515
  405.      * Return Config::DEFAULT_PHONE_NUMBER::value if not prod realm
  406.      */
  407.     public function getPhoneToRedirect()
  408.     {
  409.         $this->logTools->sms_debug($this->debug"[SmsTools::getPhoneToRedirect] try to fetch phone redirect");
  410.         if ($this->prodRealm)
  411.         {
  412.             return null;
  413.         }
  414.         $configRepository $this->em->getRepository(Config::class);
  415.         $configPhone $configRepository->findOneByName(Config::DEFAULT_PHONE_NUMBER);
  416.         if ($configPhone === null)
  417.         {
  418.             $msg "Config::DEFAULT_PHONE_NUMBER is null";
  419.             $this->logTools->errorlog($msg);
  420.             $this->logTools->smsLogDebug($msg);
  421.             return null;
  422.         }
  423.         $this->logTools->sms_debug($this->debug"[SmsTools::getPhoneToRedirect] Get phone redirect from Config : " $configPhone->getValue());
  424.         return $configPhone->getValue();
  425.     }
  426.     /**
  427.      * getPhoneNumberForSocietyGroup
  428.      *
  429.      * Gets the Phone Number for the SocietyGroup
  430.      * depending on the Platform
  431.      */
  432.     public function getPhoneNumberForSocietyGroup(SocietyGroup $societyGroup)
  433.     {
  434.         $activeSmsProvider $this->em->getRepository(SmsProvider::class)->findOneBy(array(
  435.             'isActive'        =>    1,
  436.         ));
  437.         $smsPhone $this->em
  438.             ->getRepository(SmsPhoneNumber::class)
  439.             ->findOneBySocietyGroup($societyGroup);
  440.         if ($smsPhone === null)
  441.         {
  442.             // Twilio : We need a phone number
  443.             if ($activeSmsProvider->isSmsTwilio())
  444.             {
  445.                 $msg "SmsPhoneNumber is null for societyGroup ".$societyGroup->displayForLog();
  446.                 $this->logTools->errorlog($msg);
  447.                 $this->logTools->smsLogDebug($msg$activeSmsProvider);
  448.                 return null;
  449.             }
  450.             // SmsMode : We don't need a phone number, just a short code
  451.             if ($activeSmsProvider->isSmsMode())
  452.             {
  453.                 $emitterNumber SmsMode::DEFAULT_SHORTCODE;
  454.                 return $emitterNumber;
  455.             }
  456.         }
  457.         // If we are here it means a SmsPhoneNumber has been found for this SocietyGroup
  458.         // SmsMode : We don't need a phone number, just a short code
  459.         if ($activeSmsProvider->isSmsMode())
  460.         {
  461.             $emitterNumber $this->smsProviderTools->getSmsModeSenderDisplay($societyGroup);
  462.             return $emitterNumber;
  463.         }
  464.         // Twilio : We need an actual phone number
  465.         if ($activeSmsProvider->isSmsTwilio())
  466.         {
  467.             if ($this->devRealm)
  468.             {
  469.                 $number $smsPhone->getNumberDev();
  470.             }
  471.             else if ($this->testRealm)
  472.             {
  473.                 $number $smsPhone->getNumberTest();
  474.             }
  475.             else if ($this->prodRealm)
  476.             {
  477.                 $number $smsPhone->getNumber();
  478.             }
  479.             if (!isset($number) || empty($number))
  480.             {
  481.                 $msg "SmsPhoneNumber has no number for societyGroup ".$societyGroup->displayForLog();
  482.                 if ($this->devRealm$msg .= " [dev.realm]";
  483.                 if ($this->testRealm$msg .= " [test.realm]";
  484.                 if ($this->prodRealm$msg .= " [prod.realm]";
  485.                 $this->logTools->errorlog($msg);
  486.                 $this->logTools->smsLogDebug($msg$activeSmsProvider);
  487.                 return null;
  488.             }
  489.             return $number;
  490.         }
  491.         // This code should not be reached
  492.         return null;
  493.     }
  494.     /**
  495.      * getSmsStatus
  496.      *
  497.      * Returns the corresponding SmsStatus
  498.      * for the status of the given SmsProvider (SmsTwilio|SmsMode)
  499.      */
  500.     public function getSmsStatus($smsProvider)
  501.     {
  502.         $smsProviderStatus $smsProvider->getStatus();
  503.         if ($smsProviderStatus === null)
  504.         {
  505.             return null;
  506.         }
  507.         $code $smsProviderStatus->getCode();
  508.         $smsStatus $this->em->getRepository(SmsStatus::class)->findOneByCode($code);
  509.         if ($smsStatus === null)
  510.         {
  511.             return null;
  512.         }
  513.         return $smsStatus;
  514.     }
  515.     /**
  516.      * Get total stats numberSms/price per Society
  517.      */
  518.     public function getStatistics(SocietyGroup $societyGroup)
  519.     {
  520.         $statsPerSociety $this->em->getRepository(Sms::class)->getSmsCountAndTotalAmountBySociety($societyGroup);
  521.         // Add societies that are not in the stats
  522.         $societies $societyGroup->getSocieties();
  523.         foreach($societies as $society)
  524.         {
  525.             $found false;
  526.             foreach($statsPerSociety as $societyStats)
  527.             {
  528.                 if ($society->getId() === $societyStats["societyId"])
  529.                 {
  530.                     $found true;
  531.                     break;
  532.                 }
  533.             }
  534.             if (!$found)
  535.             {
  536.                 $statsPerSociety[] = [
  537.                     "societyId"     => $society->getId(),
  538.                     "societyName"     => $society->getName(),
  539.                     "numberSms"     => 0,
  540.                     "totalPrice"     => 0,
  541.                 ];
  542.             }
  543.         }
  544.         // Sort by society name
  545.         usort($statsPerSociety, function($a$b) {
  546.             return $a["societyName"] <=> $b["societyName"];
  547.         });
  548.         return $statsPerSociety;
  549.     }
  550.     public function getStatisticsForEnvironment(SocietyGroup $societyGroup): array
  551.     {
  552.         // Raw stats: per society + per month
  553.         $societyStats $this->em->getRepository(Sms::class)->getSmsCountByMonthAndSociety($societyGroup);
  554.         
  555.         // â€œPeriod format: Y-m â€” must match with the one in SmsRepository::getSmsCountByMonthAndSociety
  556.         // Keep only current-month rows
  557.         $currentPeriod = (new \DateTime())->format('Y-m');
  558.         $currentMonthStats array_values(array_filter(
  559.             $societyStats,
  560.             static function (array $row) use ($currentPeriod): bool {
  561.                 return $row['period'] === $currentPeriod;
  562.             }
  563.         ));
  564.         // Total for the whole group for the current month
  565.         $totalSmsCurrentMonth array_sum(array_column($currentMonthStats'numberSms'));
  566.         $totalSms array_sum(array_column($societyStats'numberSms'));
  567.         
  568.         // Get quota per month
  569.         $quotaPerMonthOptionConfig $this->em->getRepository(OptionConfig::class)->findOneBy(array(
  570.             'societyGroup'        =>    $societyGroup,
  571.             'code'                =>    OptionConfig::SMS_QUOTA_PER_MONTH_CODE,
  572.         ));
  573.         $quotaPerMonth 0;
  574.         if ($quotaPerMonthOptionConfig !== null)
  575.         {
  576.             $quotaPerMonth $quotaPerMonthOptionConfig->getValue();
  577.         }
  578.         return [
  579.             'societyStats'                 => $societyStats,
  580.             'currentMonthSocietyStats'     => $currentMonthStats,
  581.             'currentMonthTotalSms'         => $totalSmsCurrentMonth,
  582.             'globalTotalSms'            => $totalSms,
  583.             'quotaPerMonth'                => $quotaPerMonth,
  584.         ];
  585.     }
  586.     /**
  587.      * Creates a unique reply-to code
  588.      * Stores it to the Sms object
  589.      * Updates the body of the Sms
  590.      * Note : Sms->body should be set
  591.      */
  592.     public function craftClientResponseCode(Sms $sms)
  593.     {
  594.         $activeSmsProvider $this->em->getRepository(SmsProvider::class)->findOneBy(array(
  595.             'isActive'        =>    1,
  596.         ));
  597.         $backTrace debug_backtrace()[1];
  598.         $method $backTrace['function'];
  599.         $class $backTrace['class'];
  600.         $trace $class."::".$method;
  601.         $trace str_replace("\\""::"$trace);
  602.         if ($sms->getProvider() === null || !$sms->getProvider()->isSmsMode())
  603.         {
  604.             // Only SMSMode for now
  605.             // return false;
  606.         }
  607.         // Craft response code
  608.         $code $this->passwordTools->generateSmsClientResponseCode();
  609.         // Craft reply-to URL
  610.         $configRepository $this->em->getRepository(Config::class);
  611.         $urlConfig $configRepository->findOneByName(Config::URL);
  612.         if ($urlConfig === null)
  613.         {
  614.             $msg "Config::URL not found";
  615.             $this->logTools->errorlog($msg);
  616.             $this->logTools->smsLogDebug($msg$activeSmsProvider);
  617.             return false;
  618.         }
  619.         $urlConfig $urlConfig->getValue();
  620.         $replyToUrl $urlConfig $this->router->generate('sms_client_response', array('code' => $code));
  621.         // $url = "<a href='".$replyToUrl."'>$replyToUrl</a>";
  622.         // This seems to be enough
  623.         $url $replyToUrl;
  624.         // Get reply text info
  625.         $info $this->translator->trans('sms_mode_reply_link_info');
  626.         // Put them all together
  627.         $info "\n" $info " " $url;
  628.         // Save them to the Sms
  629.         $sms->setClientResponseCode($code);
  630.         $sms->setCodeInfo($info);
  631.         $sms->setIncludeReplyCode(true);
  632.         $this->logTools->sms_debug($this->debug"[SmsTools :: craftClientResponseCode] code = ".$code);
  633.         $this->logTools->sms_debug($this->debug"[SmsTools :: craftClientResponseCode] info = ".$info);
  634.         return true;
  635.     }
  636.     /**
  637.      * Test if a Phone Number is compliant for sms (Twilio/SmsMode)
  638.      */
  639.     public function phoneFormat($phone)
  640.     {
  641.         $activeSmsProvider $this->em->getRepository(SmsProvider::class)->findOneBy(array(
  642.             'isActive'        =>    1,
  643.         ));
  644.         if (empty($phone))
  645.         {
  646.             $msg "PhoneNumber is empty";
  647.             $this->logTools->smsLogDebug($msg$activeSmsProvider);
  648.             return null;
  649.         }
  650.         // Delete unwanted characters (. - /) and all space characters
  651.         $phone preg_replace('/\s+/'''$phone);
  652.         $phone str_replace('.'''$phone);
  653.         $phone str_replace('-'''$phone);
  654.         $phone str_replace('/'''$phone);
  655.         // Replaces the national format with the international format
  656.         $nationalFormat preg_match('/^(06|07)/'$phone);
  657.         if ($nationalFormat === 1)
  658.         {
  659.             $phone preg_replace('/^(0)/''+33'$phone);
  660.             // Check the size is equals to 12 --> +33 6 66 66 66 66
  661.             if (strlen($phone) !== 12)
  662.             {
  663.                 $msg "PhoneNumber has more or less than 12 char : ".$phone;
  664.                 $this->logTools->smsLogDebug($msg$activeSmsProvider);
  665.                 return null;
  666.             }
  667.         }
  668.         // Plan.io Task #3785
  669.         // Harmonisation of France's international dialling code to the same format
  670.         $checkDiallingCode preg_match('/^00/'$phone);
  671.         if ($checkDiallingCode === 1)
  672.         {
  673.             $phone preg_replace('/^00/''+'$phone);
  674.             // Check the size is equals to 12 --> +33 6 66 66 66 66
  675.             if (strlen($phone) !== 12)
  676.             {
  677.                 $msg "PhoneNumber has more or less than 12 char : ".$phone;
  678.                 $this->logTools->smsLogDebug($msg$activeSmsProvider);
  679.                 return null;
  680.             }
  681.         }
  682.         // Plan.io Task #3744
  683.         // Check that the number includes the international dialling code for France
  684.         // Check if it is a mobile number
  685.         $frenchMobilePhone substr($phone04);
  686.         if (($frenchMobilePhone != "+336" && $frenchMobilePhone != "+337"))
  687.         {
  688.             $msg "PhoneNumber starts with other than +336 or +337 : ".$phone;
  689.             $this->logTools->smsLogDebug($msg$activeSmsProvider);
  690.             return null;
  691.         }
  692.         // Plan.io Task #3785
  693.         // Check if the number starts with +33 and contains only 9 digits
  694.         $checkAllGood preg_match('/^\+33\d{9}$/'$phone);
  695.         if (!$checkAllGood)
  696.         {
  697.             $msg "PhoneNumber does not start with +33 or doen not have only digits : ".$phone;
  698.             $this->logTools->smsLogDebug($msg$activeSmsProvider);
  699.             return null;
  700.         }
  701.         return $phone;
  702.     }
  703.     /**
  704.      * Plan.io Task #4515
  705.      * Get all phones available for an Individual
  706.      * Phone::numberFormatted will be set
  707.      */
  708.     public function getIndividualPhonesForSms(Individual $individual)
  709.     {
  710.         $phonesForSms = [];
  711.         $phones $this->em->getRepository(Phone::class)->findBy(array('individual' => $individual));
  712.         $mainPhone $individual->getPhone();
  713.         foreach ($phones as $phone)
  714.         {
  715.             $numberFormatted $this->phoneFormat($phone->getNumber());
  716.             if ($numberFormatted === null)
  717.             {
  718.                 continue;
  719.             }            
  720.             $phone->setNumberFormatted($numberFormatted);
  721.             $phonesForSms[] = $phone;
  722.         }
  723.         // Sort and place main at first
  724.         usort($phonesForSms, function($phoneA$phoneB) use ($mainPhone) {
  725.             if ($phoneA->getNumber() === $mainPhone) return -1;
  726.             if ($phoneB->getNumber() === $mainPhone) return 1;
  727.             return 0;
  728.         });
  729.         return $phonesForSms;
  730.     }
  731.     public function monthlyCostUpdateSociety($price$society$societyGroup)
  732.     {
  733.         $date  = new \DateTime();
  734.         $year  date_format($date,"Y");
  735.         $month date_format($date,"m");
  736.         $monthPriceSociety $this->em
  737.             ->getRepository(SmsMonthlyCostSociety::class)
  738.             ->getMonthPrice($year$month$society);
  739.         if (array_key_exists(0$monthPriceSociety))
  740.         {
  741.             $monthCost $monthPriceSociety[0];
  742.             $newPrice $monthCost->getPrice() + $price;
  743.             $monthCost->setPrice($newPrice);
  744.             $this->em->persist($monthCost);
  745.         }
  746.         else if ($society !== null)
  747.         {
  748.             $monthCost = new SmsMonthlyCostSociety();
  749.             $monthCost->setPrice($price);
  750.             $monthCost->setYear($year);
  751.             $monthCost->setMonth($month);
  752.             $monthCost->setSociety($society);
  753.             $monthCost->setSocietyGroup($societyGroup);
  754.             $this->em->persist($monthCost);
  755.         }
  756.     }
  757.     public function monthlyCostUpdateSocietyGroup($price$societyGroup)
  758.     {
  759.         $date  = new \DateTime();
  760.         $year  date_format($date,"Y");
  761.         $month date_format($date,"m");
  762.         $monthPriceSocietyGroup $this->em
  763.             ->getRepository(SmsMonthlyCostSocietyGroup::class)
  764.             ->getMonthPrice($year,$month,$societyGroup);
  765.         if (array_key_exists(0$monthPriceSocietyGroup))
  766.         {
  767.             $monthCost $monthPriceSocietyGroup[0];
  768.             $newPrice $monthCost->getPrice() + $price;
  769.             $monthCost->setPrice($newPrice);
  770.             $this->em->persist($monthCost);
  771.         }
  772.         else if ($societyGroup !== null)
  773.         {
  774.             $monthCost = new SmsMonthlyCostSocietyGroup();
  775.             $monthCost->setPrice($price);
  776.             $monthCost->setYear($year);
  777.             $monthCost->setMonth($month);
  778.             $monthCost->setSocietyGroup($societyGroup);
  779.             $this->em->persist($monthCost);
  780.         }
  781.     }
  782.     public function isValidClientData($object)
  783.     {
  784.         $activeSmsProvider $this->em->getRepository(SmsProvider::class)->findOneBy(array(
  785.             'isActive'        =>    1,
  786.         ));
  787.         if ($object instanceof Client)
  788.         {
  789.             $individual $object->getIndividual();
  790.             if ($individual === null)
  791.             {
  792.                 // Only individuals for now
  793.                 $msg "Individual is null for client ".$object->displayForLog();
  794.                 $this->logTools->errorlog($msg);
  795.                 $this->logTools->smsLogDebug($msg$activeSmsProvider);
  796.                 return null;
  797.             }
  798.             return array(
  799.                 'client'            =>    $object,
  800.                 'individual'        =>    $individual,
  801.                 'mission'            =>    null,
  802.             );
  803.         }
  804.         if ($object instanceof Mission)
  805.         {
  806.             $client $object->getReceiver();
  807.             if ($client === null)
  808.             {
  809.                 // This should not happen
  810.                 $msg "Receiver is null for mission ".$object->displayForLog();
  811.                 $this->logTools->errorlog($msg);
  812.                 $this->logTools->smsLogDebug($msg$activeSmsProvider);
  813.                 return null;
  814.             }
  815.             $individual $client->getIndividual();
  816.             if ($individual === null)
  817.             {
  818.                 // Only individuals for now
  819.                 $msg "Individual is null for client ".$client->displayForLog();
  820.                 $this->logTools->errorlog($msg);
  821.                 $this->logTools->smsLogDebug($msg$activeSmsProvider);
  822.                 return null;
  823.             }
  824.             return array(
  825.                 'client'            =>    $client,
  826.                 'individual'        =>    $individual,
  827.                 'mission'            =>    $object,
  828.             );
  829.         }
  830.         if ($object instanceof Devis || $object instanceof Invoice || $object instanceof Task)
  831.         {
  832.             $client $object->getReceiver();
  833.             if ($client === null)
  834.             {
  835.                 // This should not happen
  836.                 $msg "Receiver is null for devis | invoice | task ".$object->displayForLog();
  837.                 $this->logTools->errorlog($msg);
  838.                 $this->logTools->smsLogDebug($msg$activeSmsProvider);
  839.                 return null;
  840.             }
  841.             $mission $object->getMission();
  842.             if ($mission === null)
  843.             {
  844.                 // This should not happen
  845.                 $msg "Mission is null for devis | invoice | task ".$object->displayForLog();
  846.                 $this->logTools->errorlog($msg);
  847.                 $this->logTools->smsLogDebug($msg$activeSmsProvider);
  848.                 return null;
  849.             }
  850.             $individual $client->getIndividual();
  851.             if ($individual === null)
  852.             {
  853.                 // Only individuals for now
  854.                 $msg "Individual is null for client ".$client->displayForLog();
  855.                 $this->logTools->errorlog($msg);
  856.                 $this->logTools->smsLogDebug($msg$activeSmsProvider);
  857.                 return null;
  858.             }
  859.             return array(
  860.                 'client'            =>    $client,
  861.                 'individual'        =>    $individual,
  862.                 'mission'            =>    $mission,
  863.             );
  864.         }
  865.         // This code should not be reached
  866.         return null;
  867.     }
  868. }