src/Service/SalesForceConnector.php line 639

Open in your IDE?
  1. <?php
  2. namespace App\Service;
  3. use App\Entity\VoyageInterface;
  4. use App\Exception\WebServiceException;
  5. use App\Service\Payment\PaymentEventInterface;
  6. use App\Service\Payment\HiPay\HiPayManager;
  7. use App\Service\Process\ProcessInterface;
  8. use Psr\Log\LoggerInterface;
  9. use Symfony\Component\HttpFoundation\RequestStack;
  10. use Symfony\Contracts\Cache\ItemInterface;
  11. use Symfony\Contracts\HttpClient\HttpClientInterface;
  12. use Symfony\Contracts\Translation\TranslatorInterface;
  13. use Symfony\Contracts\Cache\CacheInterface;
  14. use Symfony\Contracts\Cache\TagAwareCacheInterface;
  15. use Symfony\Component\HttpClient\Exception\TimeoutException;
  16. use Symfony\Component\HttpClient\Exception\TransportException;
  17. use App\Exception\LMDVException;
  18. use App\Entity\GIRResaWeb\Etape3;
  19. use App\Service\Process\GIRDirectAgenceProcess;
  20. use App\Service\Process\GIRDirectMailProcess;
  21. use App\Service\Process\GIRDistribProcess;
  22. use App\Service\Process\GIRResaWebProcess;
  23. use App\Service\Process\GRPFactIndivAgenceProcess;
  24. use App\Service\Process\GRPFactIndivMailProcess;
  25. use App\Service\Process\GRPFactIndivWebProcess;
  26. /**
  27.  * Service class that handles all webservice operations againts SalesForce.
  28.  * It implements a cache (per session) that allows to call "getInfo()/getQA" multiple
  29.  * times to re-read partial JSON trees multiple times without the need to store the whole response
  30.  * in the $_SESSION and without making multiples network requests.
  31.  *
  32.  * @author jra
  33.  *
  34.  */
  35. class SalesForceConnector implements SalesForceConnectorInterface {
  36.   protected const ERROR_TYPE_SAPEIG 'SAPEIG';
  37.   protected const ERROR_TYPE_NETWORK 'NETWORK';
  38.   protected const ERROR_TYPE_SALESFORCE 'SALESFORCE';
  39.   protected const EXPIRETIME_SF 60/* 1 minute shared between sessions */
  40.   protected const EXPIRETIME_APEX 600/* 10 minutes not shared between session */
  41.   protected const CACHE_BETA_EXPIRATION 0;
  42.   protected const HTTPCLIENT_TIMEOUT 60// APEX WS give timeout at 30 secs normally
  43.   public const CACHE_TAG_GETINFO 'getInfo';
  44.   public const CACHE_TAG_GETQA 'getQA';
  45.   public const CACHE_TAG_CREATEBOOKING 'createBooking';
  46.   public const CACHE_TAG_GETPDF 'getPDF';
  47.   public const CACHE_TAG_GETCODEAGENT 'getCodeAgent';
  48.   public const CACHE_TAG_GETSALESFORCEDATA 'getSalesForceData';
  49.   public const CACHE_TAG_GETSALESFORCEPARTICIPANTS 'getSalesForceParticipants';
  50.   public const CACHE_TAG_GETSALESFORCERESPONSABLE 'getSalesForceResponsable';
  51.   public const CACHE_TAG_GETSALESFORCETITLEVOYAGE 'getSalesForceTitleVoyage';
  52.   /**
  53.    * @var \Symfony\Component\HttpFoundation\Request
  54.    */
  55.   protected $request;
  56.   /**
  57.    * @var \Symfony\Contracts\HttpClient\HttpClientInterface
  58.    */
  59.   protected $client;
  60.   /**
  61.    * @var \Psr\Log\LoggerInterface
  62.    */
  63.   protected $logger;
  64.   /**
  65.    * @var \Symfony\Contracts\Cache\TagAwareCacheInterface
  66.    */
  67.   protected $lmdvCache;
  68.   protected $config;
  69.   /**
  70.    * @var \Symfony\Contracts\Translation\TranslatorInterface
  71.    */
  72.   protected $translator;
  73.   /**
  74.    * @var \App\Service\SalesForceAuthenticatorInterface
  75.    */
  76.   protected $sf_auth;
  77.   /**
  78.    * @var \App\Service\PrintLMDVInterface
  79.    */
  80.   protected $print;
  81.   /**
  82.    * Constructor with dependency injection.
  83.    *
  84.    * @param RequestStack $request_stack
  85.    * @param HttpClientInterface $client
  86.    * @param LoggerInterface $logger
  87.    * @param array $config
  88.    */
  89.   public function __construct(RequestStack $request_stackHttpClientInterface $clientLoggerInterface $logger, array $configSalesForceAuthenticatorInterface $sf_authTagAwareCacheInterface $lmdvCacheTranslatorInterface $translatorPrintLMDVInterface $print)
  90.   {
  91.       $this->request =  $request_stack->getCurrentRequest();
  92.       $this->client $client;
  93.       $this->logger $logger;
  94.       $this->sf_auth $sf_auth;
  95.       // Cache helper
  96.       $this->lmdvCache $lmdvCache;
  97.       $this->config $config;
  98.       $this->translator $translator;
  99.       $this->print $print;
  100.   }
  101.   /**
  102.    * Gets the data needed to call getInfo() from SF with an API call.
  103.    *
  104.    * @param string $idOppSF
  105.    *    This opportunity can be FILLE or PARENT.
  106.    * @param bool $noValidation
  107.    *    if TRUE, the WS result will be returned as is, without verifying any data. By default we check the returned values.
  108.    * @throws WebServiceException
  109.    * @return array
  110.    */
  111.   public function getSalesForceData($idOppSF$noValidation FALSE) {
  112.       if($_ENV['WEBSERVICE_FAKE_DATA']) {
  113.         // This is an example of an opp. PARENT
  114.         $result = [
  115.           'idOppSFParent' => '',
  116.           'businessCodeParent' => '',
  117.           'businessCode' => '000001',
  118.           'agentCode' => 'SAP',
  119.           'productCode' => 'CDPE1',
  120.           'departureDate' => '2020-11-20',
  121.           'endDate' => '2020-12-04',
  122.           'departureCity' => 'PAR',
  123.           'adults' => 2,
  124.           'children' => 0,
  125.           'stageName' => 'Accord client'
  126.         ];
  127.         // With this parameter we simulate an opp FILLE
  128.         if($this->request->query->get('opportunity_type') == 'fille') {
  129.           $result['idOppSFParent'] = "0063O000004uB7QQAU";
  130.           $result['businessCodeParent'] = '300382';
  131.         }
  132.         return $result;
  133.       }
  134.       $cacheKey = [
  135.           'method' => 'getSalesForceData',
  136.           'idOppSF' => $idOppSF,
  137.           'noValidation' => $noValidation,
  138.       ];
  139.       $cacheKey md5(json_encode($cacheKey));
  140.       $timestamp time();
  141.       $cachedContent $this->lmdvCache->get($cacheKey, function (ItemInterface $item) use ($idOppSF) {
  142.         $item->expiresAfter(self::EXPIRETIME_SF);
  143.         $item->tag(self::CACHE_TAG_GETSALESFORCEDATA);
  144.         $this->sf_auth->authenticate();
  145.         $url $this->sf_auth->getInstanceUrl() . '/services/data/v48.0/sobjects/Opportunity/' $idOppSF;
  146.         $fields = [];
  147.         $fields[] = 'Opportunit_Parent__c'// id opp PARENT
  148.         $fields[] = 'Opportunit_Parent__r.Account.Id_Sapeig__c'// businessCode PARENT
  149.         $fields[] = 'Opportunit_Parent__r.ID_Current_Watabi__c'// PDF reference field PARENT
  150.         $fields[] = 'ID_Current_Watabi__c'// PDF reference field FILLE
  151.         $fields[] = 'Account.ID_Sapeig__c';  // businessCode FILLE
  152.         $fields[] = 'Owner.Code_Vendeur__c'// agentCode
  153.         $fields[] = 'Product__r.ProductCode';  // productCode
  154.         $fields[] = 'Departure_date__c'// departureDate
  155.         $fields[] = 'Arrival_date__c'// endDate
  156.         $fields[] = 'Lead_departure_city__c'// departureCity
  157.         $fields[] = 'Number_of_adults_lead__c'// adults
  158.         $fields[] = 'Number_of_kids_lead__c'// children
  159.         $fields[] = 'Opportunit_Parent__r.StageName'// statut de l'opp PARENT
  160.         $fields[] = 'StageName'// statut de l'opp FILLE
  161.         $fields[] = 'RecordType.DeveloperName';
  162.         $fields[] = 'Product_code_lead__c';
  163.         $url .= '?fields=' implode(','$fields);
  164.         try {
  165.           $response $this->client->request('GET'$url, [
  166.               'timeout' => self::HTTPCLIENT_TIMEOUT,
  167.               'headers' => [
  168.                 'Authorization' => "Bearer " $this->sf_auth->getAccessToken(),
  169.               ],
  170.           ]);
  171.           $content = [];
  172.           $content['url'] = $url;
  173.           $content['timestamp'] = time();
  174.           $content['statusCode'] = $response->getStatusCode();
  175.           $content['response'] = $response->toArray(FALSE);
  176.         }
  177.         catch(\Exception $e) {
  178.           if($e instanceof TransportException) {
  179.             $httpLogs = [/*$response->getInfo('debug')*/];
  180.             $content['response'] = $e->getMessage();
  181.             $this->logger->error('LMDV SF Api Error (not cached) getSalesForceData', ['debug' => $httpLogs'url' => $url'request' => '''response' => '']);
  182.             throw new WebServiceException($this->translator->trans('error.page.label_timeout'), LMDVException::ERROR_TIMEOUT);
  183.           }
  184.           if($response->getStatusCode() == 401) {
  185.             $this->sf_auth->invalidateAccessToken();
  186.           }
  187.           $httpLogs = [/*$response->getInfo('debug')*/];
  188.           $content['response'] = $e->getMessage();
  189.           $this->logger->error('LMDV SF Api Error (not cached) getSalesForceData', ['debug' => $httpLogs'url' => $url'request' => '''response' => $content['response']]);
  190.           throw new WebServiceException($this->translator->trans("error.webservice.sf_api_error", ['message' => $response->getStatusCode()]), LMDVException::ERROR_SALESFORCE);
  191.         }
  192.         $content['httpClient']['debug'] = [/*$response->getInfo('debug')*/];
  193.         $content['httpClient']['request'] = '';
  194.         return $content;
  195.       }, self::CACHE_BETA_EXPIRATION);
  196.       $cached '(not cached)';
  197.       if($cachedContent['timestamp'] < $timestamp) {
  198.         $cached '(cached)';
  199.       }
  200.       if($cachedContent['statusCode'] != 200) {
  201.         $this->logger->error("LMDV SF Api Error $cached getSalesForceData", ['debug' =>  $cachedContent['httpClient']['debug'], 'url' => $cachedContent['url'], 'request' =>  $cachedContent['httpClient']['request'], 'response' => $cachedContent['response']]);
  202.         //$this->lmdvCache->delete($cacheKey);
  203.         throw new WebServiceException($this->translator->trans("error.webservice.sf_api_error", ['message' => print_r($cachedContent['response'], TRUE)]), LMDVException::ERROR_SALESFORCE);
  204.       }
  205.       if(! isset($cachedContent['response']['Id']) || $cachedContent['response']['Id'] === FALSE) {
  206.         $this->logger->error("LMDV SF Api Error $cached getSalesForceData", ['debug' => $cachedContent['httpClient']['debug'], 'url' => $cachedContent['url'], 'request' =>  $cachedContent['httpClient']['request'], 'response' => $cachedContent['response']]);
  207.         //$this->lmdvCache->delete($cacheKey);
  208.         throw new WebServiceException($this->translator->trans("error.webservice.sf_api_error", ['message' => "There s no Id key"]), LMDVException::ERROR_SALESFORCE);
  209.       }
  210.       $this->logger->info("LMDV SF Api Success $cached", ['debug' => $cachedContent['httpClient']['debug'], 'url' => $cachedContent['url'], 'request' =>  $cachedContent['httpClient']['request'], 'response' => $cachedContent['response']]);
  211.       ///////////////////////////////////////////////////////////////////
  212.       // Here we build the return array with the needed information
  213.       ///////////////////////////////////////////////////////////////////
  214.       $r $cachedContent['response'];
  215.       $result = [
  216.         'idOppSFParent' => $r['Opportunit_Parent__c'],
  217.         'businessCodeParent' => isset($r['Opportunit_Parent__r']) ? $r['Opportunit_Parent__r']['Account']['ID_Sapeig__c'] : NULL,
  218.         'businessCode' => $r['Account']['ID_Sapeig__c'],
  219.         'reference_pdf' => isset($r['Opportunit_Parent__r']) ? $r['Opportunit_Parent__r']['ID_Current_Watabi__c'] : $r['ID_Current_Watabi__c'],
  220.         'agentCode' => $r['Owner']['Code_Vendeur__c'],
  221.         'productCode' => $r['Product__r']['ProductCode'] ? $r['Product__r']['ProductCode'] : ($r['Product_code_lead__c'] ? $r['Product_code_lead__c'] : $this->request->getSession()->get('productCode')), // <- this default is needed by GRPFactIndivWeb
  222.         'departureDate' => $r['Departure_date__c'] ? $r['Departure_date__c'] : $this->request->getSession()->get('departureDate'), // <- this default is needed by GRPFactIndivWeb
  223.         'endDate' => $r['Arrival_date__c'],
  224.         //'departureCity' => $r['Lead_departure_city__c'] ? $r['Lead_departure_city__c'] : 'PAR',
  225.         'departureCity' => 'PAR',
  226.         'adults' => $r['Number_of_adults_lead__c'] ? intval($r['Number_of_adults_lead__c']) : '1',
  227.         'children' => $r['Number_of_kids_lead__c'] ? intval($r['Number_of_kids_lead__c']) : '0',
  228.         'type' => $r['RecordType']['DeveloperName'],
  229.         'stageName' => isset($r['Opportunit_Parent__r']) ? $r['Opportunit_Parent__r']['StageName'] : $r['StageName'],
  230.         'stageNameFille' => $r['StageName'],
  231.       ];
  232.       if($noValidation) {
  233.         //$this->lmdvCache->delete($cacheKey);
  234.         return $result;
  235.       }
  236.       // NOTE : agentCode is not required for the tunnel to work proprely. businessCode is optional too, it will be set as "000001" by Gauthier if not present here.
  237.       $is_empty = empty($result['productCode']) || empty($result['departureDate']) || empty($result['endDate']);
  238.       if($is_empty) {
  239.         $this->logger->error("LMDV SF Api Error $cached getSalesForceData", ['debug' =>  $cachedContent['httpClient']['debug'], 'url' => $cachedContent['url'], 'request' =>  $cachedContent['httpClient']['request'], 'response' => $cachedContent['response']]);
  240.         //$this->lmdvCache->delete($cacheKey);
  241.         if(empty($result['productCode'])) {
  242.           throw new WebServiceException($this->translator->trans("error.webservice.sf_api_error", ['message' => "The productCode is empty."]), LMDVException::ERROR_SALESFORCE);
  243.         }
  244.         elseif(empty($result['departureDate'])) {
  245.           throw new WebServiceException($this->translator->trans("error.webservice.sf_api_error", ['message' => "The departureDate is empty."]), LMDVException::ERROR_SALESFORCE);
  246.         }
  247.         elseif(empty($result['endDate'])) {
  248.           throw new WebServiceException($this->translator->trans("error.webservice.sf_api_error", ['message' => "The endDate is empty."]), LMDVException::ERROR_SALESFORCE);
  249.         }
  250.         else {
  251.           throw new WebServiceException($this->translator->trans("error.webservice.sf_api_error", ['message' => "Missing required information :" print_r($resultTRUE)]), LMDVException::ERROR_SALESFORCE);
  252.         }
  253.       }
  254.       return $result;
  255.   }
  256.   /**
  257.    * Gets the travellers from SF with an API call
  258.    *
  259.    * @param string $idOppSF
  260.    * @throws WebServiceException
  261.    * @return array
  262.    */
  263.   public function getSalesForceParticipants($idOppSF) {
  264.       if($_ENV['WEBSERVICE_FAKE_DATA']) {
  265.         $result = [];
  266.         $p = [
  267.             'civilite' => 'Monsieur',
  268.             'prenom' => 'Juan Fco',
  269.             'nom' => 'Rodriguez Hervella',
  270.             'nomJeuneFille' => '',
  271.             'dateNaissance' => '10/03/1976',
  272.             'nationalite' => 'Espagnole',
  273.             'sfId' => 'TODO'
  274.         ];
  275.         $result[] = $p;
  276.         $result[] = $p;
  277.         return $result;
  278.       }
  279.       $cacheKey = [
  280.           'method' => 'getSalesForceParticipants',
  281.           'idOppSF' => $idOppSF,
  282.       ];
  283.       $cacheKey md5(json_encode($cacheKey));
  284.       $timestamp time();
  285.       $cachedContent $this->lmdvCache->get($cacheKey, function (ItemInterface $item) use ($idOppSF) {
  286.         $item->expiresAfter(self::EXPIRETIME_SF);
  287.         $item->tag(self::CACHE_TAG_GETSALESFORCEPARTICIPANTS);
  288.         $this->sf_auth->authenticate();
  289.         $url $this->sf_auth->getInstanceUrl() . '/services/data/v48.0/sobjects/Opportunity/' $idOppSF '/PAX__r';
  290.         $fields = [];
  291.         $fields[] = 'Id';
  292.         $fields[] = 'Compte__r.Salutation';
  293.         $fields[] = 'Compte__r.Firstname';
  294.         $fields[] = 'Compte__r.Lastname';
  295.         $fields[] = 'Compte__r.Maiden_name__pc';
  296.         $fields[] = 'Compte__r.Nationality__pc';
  297.         $fields[] = 'Compte__r.PersonBirthdate';
  298.         $url .= '?fields=' implode(','$fields);
  299.         try {
  300.           $response $this->client->request('GET'$url, [
  301.               'timeout' => self::HTTPCLIENT_TIMEOUT,
  302.               'headers' => [
  303.                 'Authorization' => "Bearer " $this->sf_auth->getAccessToken(),
  304.               ],
  305.           ]);
  306.           $content = [];
  307.           $content['url'] = $url;
  308.           $content['timestamp'] = time();
  309.           $content['statusCode'] = $response->getStatusCode();
  310.           $content['response'] = $response->toArray(FALSE);
  311.         }
  312.         catch(\Exception $e) {
  313.           if($e instanceof TransportException) {
  314.             $httpLogs = [/*$response->getInfo('debug')*/];
  315.             $content['response'] = $e->getMessage();
  316.             $this->logger->error('LMDV SF Api Error (not cached) getSalesForceParticipants', ['debug' => $httpLogs'url' => $url'request' => '''response' => '']);
  317.             throw new WebServiceException($this->translator->trans('error.page.label_timeout'), LMDVException::ERROR_TIMEOUT);
  318.           }
  319.           if($response->getStatusCode() == 401) {
  320.             $this->sf_auth->invalidateAccessToken();
  321.           }
  322.           $httpLogs = [/*$response->getInfo('debug')*/];
  323.           $content['response'] = $e->getMessage();
  324.           $this->logger->error('LMDV SF Api Error (not cached) getSalesForceParticipants', ['debug' => $httpLogs'url' => $url'request' => '''response' => $content['response']]);
  325.           throw new WebServiceException($this->translator->trans("error.webservice.sf_api_error", ['message' => $response->getStatusCode()]), LMDVException::ERROR_SALESFORCE);
  326.         }
  327.         $content['httpClient']['debug'] = [/*$response->getInfo('debug')*/];
  328.         $content['httpClient']['request'] = '';
  329.         return $content;
  330.       }, self::CACHE_BETA_EXPIRATION);
  331.       $cached '(not cached)';
  332.       if($cachedContent['timestamp'] < $timestamp) {
  333.         $cached '(cached)';
  334.       }
  335.       if($cachedContent['statusCode'] != 200) {
  336.         $this->logger->error("LMDV SF Api Error $cached getSalesForceParticipants", ['debug' => $cachedContent['httpClient']['debug'], 'url' => $cachedContent['url'], 'request' =>  $cachedContent['httpClient']['request'], 'response' => $cachedContent['response']]);
  337.         //$this->lmdvCache->delete($cacheKey);
  338.         throw new WebServiceException($this->translator->trans("error.webservice.sf_api_error", ['message' =>print_r($cachedContent['response'], TRUE)]), LMDVException::ERROR_SALESFORCE);
  339.       }
  340.       if(! isset($cachedContent['response']['done']) || $cachedContent['response']['done'] === FALSE) {
  341.         $this->logger->error("LMDV SF Api Error $cached getSalesForceParticipants", ['debug' => $cachedContent['httpClient']['debug'], 'url' => $cachedContent['url'], 'request' =>  $cachedContent['httpClient']['request'], 'response' => $cachedContent['response']]);
  342.         //$this->lmdvCache->delete($cacheKey);
  343.         throw new WebServiceException($this->translator->trans("error.webservice.sf_api_error", ['message' => "There is no done key"]), LMDVException::ERROR_SALESFORCE);
  344.       }
  345.       $this->logger->info("LMDV SF Api Success $cached getSalesForceParticipants", ['debug' =>  $cachedContent['httpClient']['debug'], 'url' => $cachedContent['url'], 'request' =>  $cachedContent['httpClient']['request'], 'response' => $cachedContent['response']]);
  346.       ///////////////////////////////////////////////////////////////////
  347.       // Here we build the return array with the needed information
  348.       ///////////////////////////////////////////////////////////////////
  349.       $r $cachedContent['response'];
  350.       $results = [];
  351.       foreach($r['records'] as $record) {
  352.         $result = [
  353.           'civilite' => $this->getSalutation($record['Compte__r']['Salutation']),
  354.           'prenom' => $record['Compte__r']['FirstName'],
  355.           'nom' => $record['Compte__r']['LastName'],
  356.           'nomJeuneFille' => $record['Compte__r']['Maiden_name__pc'],
  357.           'dateNaissance' =>  $record['Compte__r']['PersonBirthdate'] ? date('d/m/Y'strtotime($record['Compte__r']['PersonBirthdate'])) : '',
  358.           'nationalite' => $record['Compte__r']['Nationality__pc'],
  359.           'sfId' => $record['Id']
  360.         ];
  361.         $results[] = $result;
  362.       }
  363.       return $results;
  364.   }
  365.   protected function getSalutation($sf_value) {
  366.     $sf_value = \strtoupper($sf_value);
  367.     switch($sf_value) {
  368.       case 'MR':
  369.       case 'MR.':
  370.         $salutation 'Monsieur';
  371.         break;
  372.       case 'MRS':
  373.       case 'MS':
  374.       case 'MS.':
  375.         $salutation 'Madame';
  376.         break;
  377.       default:
  378.         $salutation 'Monsieur';
  379.         break;
  380.     }
  381.     return $salutation;
  382.   }
  383.   /**
  384.    * Gets the client qui paye information from SalesForce using an API call.
  385.    *
  386.    * @param string $idOppSF
  387.    * @throws WebServiceException
  388.    * @return array
  389.    */
  390.   public function getSalesForceResponsable($idOppSF) {
  391.       if($_ENV['WEBSERVICE_FAKE_DATA']) {
  392.         $result = [
  393.             'civilite' => 'Monsieur',
  394.             'prenom' => 'Juan Fco',
  395.             'nom' => 'Rodriguez Hervella',
  396.             'telephone' => '0669799808',
  397.             'telephone2' => '0671785250',
  398.             'adresse' => '34, Rue Defrance',
  399.             'codePostal' => '94300',
  400.             'ville' => 'Vincennes',
  401.             'pays' => 'France',
  402.             'email' => 'juan@tuxe.es',
  403.             'sfId' => 'TODO'
  404.         ];
  405.         return $result;
  406.       }
  407.       $cacheKey = [
  408.           'method' => 'getSalesForceResponsable',
  409.           'idOppSF' => $idOppSF,
  410.       ];
  411.       $cacheKey md5(json_encode($cacheKey));
  412.       $timestamp time();
  413.       $cachedContent $this->lmdvCache->get($cacheKey, function (ItemInterface $item) use ($idOppSF) {
  414.         $item->expiresAfter(self::EXPIRETIME_SF);
  415.         $item->tag(self::CACHE_TAG_GETSALESFORCERESPONSABLE);
  416.         $this->sf_auth->authenticate();
  417.         $url $this->sf_auth->getInstanceUrl() . '/services/data/v48.0/sobjects/Opportunity/' $idOppSF;
  418.         $fields = [];
  419.         $fields[] = 'Mes_commentaires_sur_le_voyage__c';
  420.         $fields[] = 'Account.Id';
  421.         $fields[] = 'Account.FirstName';
  422.         $fields[] = 'Account.LastName';
  423.         $fields[] = 'Account.Salutation';
  424.         $fields[] = 'Account.Phone';
  425.         $fields[] = 'Account.T_l_phone_2__c';
  426.         $fields[] = 'Account.PersonEmail';
  427.         $fields[] = 'Account.PersonMailingCountryCode';
  428.         $fields[] = 'Account.PersonMailingPostalCode';
  429.         $fields[] = 'Account.PersonMailingStreet';
  430.         $fields[] = 'Account.PersonMailingCity';
  431.         $url .= '?fields=' implode(','$fields);
  432.         try {
  433.           $response $this->client->request('GET'$url, [
  434.               'timeout' => self::HTTPCLIENT_TIMEOUT,
  435.               'headers' => [
  436.                 'Authorization' => "Bearer " $this->sf_auth->getAccessToken(),
  437.               ],
  438.           ]);
  439.           $content = [];
  440.           $content['url'] = $url;
  441.           $content['timestamp'] = time();
  442.           $content['statusCode'] = $response->getStatusCode();
  443.           $content['response'] = $response->toArray(FALSE);
  444.         }
  445.         catch(\Exception $e) {
  446.           if($e instanceof TransportException) {
  447.             $httpLogs = [/*$response->getInfo('debug')*/];
  448.             $content['response'] = $e->getMessage();
  449.             $this->logger->error('LMDV SF Api Error (not cached) getSalesForceResponsable', ['debug' => $httpLogs'url' => $url'request' => '''response' => '']);
  450.             throw new WebServiceException($this->translator->trans('error.page.label_timeout'), LMDVException::ERROR_TIMEOUT);
  451.           }
  452.           if($response->getStatusCode() == 401) {
  453.             $this->sf_auth->invalidateAccessToken();
  454.           }
  455.           $httpLogs = [/*$response->getInfo('debug')*/];
  456.           $content['response'] = $e->getMessage();
  457.           $this->logger->error('LMDV SF Api Error (not cached) getSalesForceResponsable', ['debug' => $httpLogs'url' => $url'request' => '''response' => $content['response']]);
  458.           throw new WebServiceException($this->translator->trans("error.webservice.sf_api_error", ['message' => $response->getStatusCode()]), LMDVException::ERROR_SALESFORCE);
  459.         }
  460.         $content['httpClient']['debug'] = [/*$response->getInfo('debug')*/];
  461.         $content['httpClient']['request'] = '';
  462.         return $content;
  463.       }, self::CACHE_BETA_EXPIRATION);
  464.       $cached '(not cached)';
  465.       if($cachedContent['timestamp'] < $timestamp) {
  466.         $cached '(cached)';
  467.       }
  468.       if($cachedContent['statusCode'] != 200) {
  469.         $this->logger->error("LMDV SF Api Error $cached getSalesForceResponsable", ['debug' => $cachedContent['httpClient']['debug'], 'url' => $cachedContent['url'], 'request' =>  $cachedContent['httpClient']['request'], 'response' => $cachedContent['response']]);
  470.         //$this->lmdvCache->delete($cacheKey);
  471.         throw new WebServiceException($this->translator->trans("error.webservice.sf_api_error", ['message' => print_r($cachedContent['response'], TRUE)]), LMDVException::ERROR_SALESFORCE);
  472.       }
  473.       if(! isset($cachedContent['response']['Id'])) {
  474.         $this->logger->error("LMDV SF Api Error $cached getSalesForceResponsable", ['debug' => $cachedContent['httpClient']['debug'], 'url' => $cachedContent['url'], 'request' =>  $cachedContent['httpClient']['request'], 'response' => $cachedContent['response']]);
  475.         //$this->lmdvCache->delete($cacheKey);
  476.         throw new WebServiceException($this->translator->trans("error.webservice.sf_api_error", ['message' => "There is no Id key"]), LMDVException::ERROR_SALESFORCE);
  477.       }
  478.       $this->logger->info("LMDV SF Api Success $cached getSalesForceResponsable", ['debug' => $cachedContent['httpClient']['debug'], 'url' => $cachedContent['url'], 'request' =>  $cachedContent['httpClient']['request'], 'response' => $cachedContent['response']]);
  479.       ///////////////////////////////////////////////////////////////////
  480.       // Here we build the return array with the needed information
  481.       ///////////////////////////////////////////////////////////////////
  482.       $r $cachedContent['response'];
  483.       $result = [
  484.         'civilite' => $this->getSalutation($r['Account']['Salutation']),
  485.         'prenom' => $r['Account']['FirstName'],
  486.         'nom' => $r['Account']['LastName'],
  487.         'telephone' => $r['Account']['Phone'],
  488.         'telephone2' => $r['Account']['T_l_phone_2__c'],
  489.         'adresse' => $r['Account']['PersonMailingStreet'],
  490.         'codePostal' => $r['Account']['PersonMailingPostalCode'],
  491.         'ville' => $r['Account']['PersonMailingCity'],
  492.         'pays' => $r['Account']['PersonMailingCountryCode'],
  493.         'email' => $r['Account']['PersonEmail'],
  494.         'sfId' => $r['Account']['Id'],
  495.         'comments' => $this->cleanUpComments($r['Mes_commentaires_sur_le_voyage__c']),
  496.       ];
  497.       // If the country is empty, assume 'FR'
  498.       if(empty($result['pays'])) {
  499.         $result['pays'] = 'FR';
  500.       }
  501.       else {
  502.         $result['pays'] = \strtoupper($result['pays']);
  503.       }
  504.       return $result;
  505.   }
  506.   /**
  507.    * Gets the title of the voyage from SalesForce using an API call. This is used only in etape0,
  508.    * the other steps will use the info retourned by getInfo()
  509.    *
  510.    * @param VoyageInterface $voyage
  511.    * @return string
  512.    *  The title of the voyage
  513.    */
  514.   public function getSalesForceTitleVoyage(VoyageInterface $voyage) {
  515.     /** @var \App\Service\Process\ProcessInterface $process */
  516.     $process $voyage->getProcess();
  517.     $title '';
  518.     try {
  519.       if($process instanceof GIRDirectAgenceProcess || $process instanceof GIRDirectMailProcess) {
  520.         // chercher l’info dans le nom de l’opportunité associée
  521.         $url '/services/data/v48.0/sobjects/Opportunity/' $voyage->getIdOppSF() . '?fields=Name';
  522.         $result =  $this->_getSalesForceAPI($url);
  523.         $title $result['Name'];
  524.       }
  525.       elseif($process instanceof GIRDistribProcess || $process instanceof GIRResaWebProcess) {
  526.         // chercher l’info dans le champ Familly_Name_LMDV__c de l’objet DEPART
  527.         $url '/services/data/v48.0/query/';
  528.         $url .= "?q=SELECT+Familly_Name_LMDV__c+FROM+Product2+WHERE+ProductCode='".$voyage->getProductCode()."'";
  529.         $url .= "+AND+Date_de_Depart__c+=+".$voyage->getDepartureDate()."+AND+Date_De_Retour__c+=".$voyage->getEndDate();
  530.         $result =  $this->_getSalesForceAPI($url);
  531.         $title $result['records'][0]['Familly_Name_LMDV__c'];
  532.       }
  533.       elseif($process instanceof GRPFactIndivAgenceProcess || $process instanceof GRPFactIndivMailProcess || $process instanceof GRPFactIndivWebProcess) {
  534.         // chercher l’info dans le nom de l’opportunité PARENT
  535.         $url '/services/data/v48.0/sobjects/Opportunity/' $voyage->getIdOppSFParent() . '?fields=Name';
  536.         $result =  $this->_getSalesForceAPI($url);
  537.         $title $result['Name'];
  538.       }
  539.     }
  540.     catch(\Exception $e) {
  541.       // Errors are ignored but saved to the log.
  542.     }
  543.     $this->request->getSession()->set('TWIG_title'$title);
  544.   }
  545.   /**
  546.    * Auxiliary function to get the Voyage PDF url from SalesForce
  547.    *
  548.    * @param VoyageInterface $voyage
  549.    * @return string
  550.    *  The voyage's url
  551.    */
  552.   protected function getUrlPdfVoyage(VoyageInterface $voyage) {
  553.     /** @var \App\Service\Process\ProcessInterface $process */
  554.     $process $voyage->getProcess();
  555.     $url_pdf '';
  556.     if($process instanceof GIRDirectAgenceProcess || $process instanceof GIRDirectMailProcess) {
  557.       // chercher l’info dans le nom de l’opportunité associée
  558.       $url '/services/data/v48.0/sobjects/Opportunity/' $voyage->getIdOppSF() . '?fields=Product__r.urlPDF__c';
  559.       $result =  $this->_getSalesForceAPI($url);
  560.       $url_pdf $result['Product__r']['urlPDF__c'];
  561.     }
  562.     elseif($process instanceof GIRDistribProcess || $process instanceof GIRResaWebProcess) {
  563.       // chercher l’info dans le champ Familly_Name_LMDV__c de l’objet DEPART
  564.       $url '/services/data/v48.0/query/';
  565.       $url .= "?q=SELECT+urlPDF__c+FROM+Product2+WHERE+ProductCode='".$voyage->getProductCode()."'";
  566.       $url .= "+AND+Date_de_Depart__c+=+".$voyage->getDepartureDate()."+AND+Date_De_Retour__c+=".$voyage->getEndDate();
  567.       $result =  $this->_getSalesForceAPI($url);
  568.       $url_pdf = @$result['records'][0]['urlPDF__c'];
  569.     }
  570.     return $url_pdf;
  571.   }
  572.   /**
  573.    * Auxiliary function that executes the SF Api call to recover the voyage's title
  574.    *
  575.    * @param string $idOppSF
  576.    * @throws WebServiceException
  577.    * @return array
  578.    */
  579.   protected function _getSalesForceAPI($url) {
  580.       $cacheKey = [
  581.           'method' => 'getSalesForceTitleVoyage',
  582.           'url' => $url,
  583.       ];
  584.       $cacheKey md5(json_encode($cacheKey));
  585.       $timestamp time();
  586.       $cachedContent $this->lmdvCache->get($cacheKey, function (ItemInterface $item) use ($url) {
  587.         $item->expiresAfter(self::EXPIRETIME_SF);
  588.         $item->tag(self::CACHE_TAG_GETSALESFORCETITLEVOYAGE);
  589.         $this->sf_auth->authenticate();
  590.         $url $this->sf_auth->getInstanceUrl() . $url;
  591.         try {
  592.           $response $this->client->request('GET'$url, [
  593.               'timeout' => self::HTTPCLIENT_TIMEOUT,
  594.               'headers' => [
  595.                 'Authorization' => "Bearer " $this->sf_auth->getAccessToken(),
  596.               ],
  597.           ]);
  598.           $content = [];
  599.           $content['url'] = $url;
  600.           $content['timestamp'] = time();
  601.           $content['statusCode'] = $response->getStatusCode();
  602.           $content['response'] = $response->toArray(FALSE);
  603.         }
  604.         catch(\Exception $e) {
  605.           if($e instanceof TransportException) {
  606.             $httpLogs = [/*$response->getInfo('debug')*/];
  607.             $content['response'] = $e->getMessage();
  608.             $this->logger->error('LMDV SF Api Error (not cached) getSalesForceTitleVoyage', ['debug' => $httpLogs'url' => $url'request' => '''response' => '']);
  609.             throw new WebServiceException($this->translator->trans('error.page.label_timeout'), LMDVException::ERROR_TIMEOUT);
  610.           }
  611.           if($response->getStatusCode() == 401) {
  612.             $this->sf_auth->invalidateAccessToken();
  613.           }
  614.           $httpLogs = [/*$response->getInfo('debug')*/];
  615.           $content['response'] = $e->getMessage();
  616.           $this->logger->error('LMDV SF Api Error (not cached) getSalesForceTitleVoyage', ['debug' => $httpLogs'url' => $url'request' => '''response' => $content['response']]);
  617.           throw new WebServiceException($this->translator->trans("error.webservice.sf_api_error", ['message' => $response->getStatusCode()]), LMDVException::ERROR_SALESFORCE);
  618.         }
  619.         $content['httpClient']['debug'] = [/*$response->getInfo('debug')*/];
  620.         $content['httpClient']['request'] = '';
  621.         return $content;
  622.       }, self::CACHE_BETA_EXPIRATION);
  623.       $cached '(not cached)';
  624.       if($cachedContent['timestamp'] < $timestamp) {
  625.         $cached '(cached)';
  626.       }
  627.       if($cachedContent['statusCode'] != 200) {
  628.         $this->logger->error("LMDV SF Api Error $cached getSalesForceTitleVoyage", ['debug' => $cachedContent['httpClient']['debug'], 'url' => $cachedContent['url'], 'request' =>  $cachedContent['httpClient']['request'], 'response' => $cachedContent['response']]);
  629.         //$this->lmdvCache->delete($cacheKey);
  630.         throw new WebServiceException($this->translator->trans("error.webservice.sf_api_error", ['message' => print_r($cachedContent['response'], TRUE)]), LMDVException::ERROR_SALESFORCE);
  631.       }
  632.       $this->logger->info("LMDV SF Api Success $cached getSalesForceTitleVoyage", ['debug' => $cachedContent['httpClient']['debug'], 'url' => $cachedContent['url'], 'request' =>  $cachedContent['httpClient']['request'], 'response' => $cachedContent['response']]);
  633.       ///////////////////////////////////////////////////////////////////
  634.       // Here we build the return array with the needed information
  635.       ///////////////////////////////////////////////////////////////////
  636.       $result $cachedContent['response'];
  637.       return $result;
  638.   }
  639.   public function getPdfVoyage(VoyageInterface $voyage) {
  640.       $result = [];
  641.       $process $voyage->getProcess();
  642.       if($process instanceof GIRDirectAgenceProcess ||
  643.             $process instanceof GIRDirectMailProcess ||
  644.             $process instanceof GIRDistribProcess ||
  645.             $process instanceof GIRResaWebProcess) {
  646.         $result['type'] = 'url';
  647.         $result['content'] = $this->getUrlPdfVoyage($voyage);
  648.       }
  649.       elseif($process instanceof GRPFactIndivAgenceProcess ||
  650.                 $process instanceof GRPFactIndivMailProcess ||
  651.                 $process instanceof GRPFactIndivWebProcess) {
  652.         if(! $voyage->getIdOppSFParent()) {
  653.           throw new \Exception('_getPdfVoyage called with empty IdOppParent');
  654.         }
  655.         $result['type'] = 'content';
  656.         $result['content'] = $this->_getPdfVoyage($process$voyage->getIdOppSFParent());
  657.       }
  658.       return $result;
  659.   }
  660.   /**
  661.    * WS APEX getPdf : this WS is cached by user for saving SF API calls and to improve app performace.
  662.    *
  663.    * This is used for GRP tunnels in order to download the travel PDF brochure.
  664.    *
  665.    * @param string $idOppSF
  666.    * @throws WebServiceException
  667.    * @return string $pdfContent
  668.    *    The binary PDF content.
  669.    */
  670.   protected function _getPdfVoyage(ProcessInterface $processstring $idOppSF) {
  671.       if($_ENV['WEBSERVICE_FAKE_DATA']) {
  672.         $data = \file_get_contents__DIR__ '/tests/grp_travel_brochure.pdf');
  673.         return $data;
  674.       }
  675.       $cacheKey = [
  676.           'method' => 'getPdf',
  677.           'idOppSF' => $idOppSF,
  678.           'sessionId' => $this->request->getSession()->getId()
  679.       ];
  680.       $cacheKey md5(json_encode($cacheKey));
  681.       $timestamp time();
  682.       $cachedContent $this->lmdvCache->get($cacheKey, function (ItemInterface $item) use ($process$idOppSF) {
  683.         $item->expiresAfter(self::EXPIRETIME_APEX);
  684.         $item->tag(self::CACHE_TAG_GETPDF);
  685.         $this->sf_auth->authenticate();
  686.         $url $this->sf_auth->getInstanceUrl() . '/services/apexrest/getPdf/?id=' $idOppSF '&process=' $process->display();
  687.         try {
  688.           $response $this->client->request('GET'$url, [
  689.               'timeout' => self::HTTPCLIENT_TIMEOUT,
  690.               'headers' => [
  691.                 'Authorization' => "Bearer " $this->sf_auth->getAccessToken(),
  692.               ],
  693.           ]);
  694.           $content = [];
  695.           $content['url'] = $url;
  696.           $content['timestamp'] = time();
  697.           $content['statusCode'] = $response->getStatusCode();
  698.           $content['response'] = $response->getContent();
  699.         }
  700.         catch(\Exception $e) {
  701.           if($e instanceof TransportException) {
  702.             $httpLogs = [/*$response->getInfo('debug')*/];
  703.             $content['response'] = $e->getMessage();
  704.             $this->logger->error('LMDV SF Api Error (not cached) getPdf', ['debug' => $httpLogs'url' => $url'request' => '''response' => '']);
  705.             throw new WebServiceException($this->translator->trans('error.page.label_timeout'), LMDVException::ERROR_TIMEOUT);
  706.           }
  707.           if($response->getStatusCode() == 401) {
  708.             $this->sf_auth->invalidateAccessToken();
  709.           }
  710.           $httpLogs = [/*$response->getInfo('debug')*/];
  711.           $content['response'] = $e->getMessage();
  712.           $this->logger->error('LMDV Webservice Error (not cached) getPdf', ['debug' => $httpLogs'url' => $url'statusCode' => $response->getStatusCode()]);
  713.           throw new WebServiceException($this->translator->trans("error.webservice.sf_api_error", ['message' => $response->getStatusCode()]), LMDVException::ERROR_SALESFORCE);
  714.         }
  715.         $content['httpClient']['debug'] = [/*$response->getInfo('debug')*/];
  716.         $content['httpClient']['request'] = '';
  717.         return $content;
  718.       }, self::CACHE_BETA_EXPIRATION);
  719.       $cached '(not cached)';
  720.       if($cachedContent['timestamp'] < $timestamp) {
  721.         $cached '(cached)';
  722.       }
  723.       $this->logger->info("LMDV Webservice Success $cached getPdf", ['debug' => $cachedContent['httpClient']['debug'], 'url' => $cachedContent['url'], 'statusCode' => $cachedContent['statusCode']]);
  724.       return $cachedContent['response'];
  725.   }
  726.   /**
  727.    * WS APEX retrieveAgentCode : this WS is cached by user for saving SF API calls and to improve app performace.
  728.    *
  729.    * @param VoyageInterface $voyage
  730.    * @throws WebServiceException
  731.    * @return array
  732.    */
  733.   public function getCodeAgent(VoyageInterface $voyage) {
  734.       if($_ENV['WEBSERVICE_FAKE_DATA']) {
  735.         $data = [
  736.           'agentCode' => 'MAR'
  737.         ];
  738.         return $data;
  739.       }
  740.       $body = [];
  741.       $body['dto'] = [
  742.           'requesterCode' => $voyage->getEtape0()->getCodeSociete(),
  743.           'firstName' => $voyage->getEtape0()->getPrenomVendeur(),
  744.           'lastName' => $voyage->getEtape0()->getNomVendeur(),
  745.           'phone' => $voyage->getEtape0()->getTelVendeur(),
  746.           'email' => $voyage->getEtape0()->getMailVendeur(),
  747.           'process' => $voyage->getProcess()->display()
  748.       ];
  749.       $cacheKey $body + [
  750.           'method' => 'retrieveAgentCode',
  751.           'sessionId' => $voyage->getSessionId()
  752.       ];
  753.       $cacheKey md5(json_encode($cacheKey));
  754.       $timestamp time();
  755.       $cachedContent $this->lmdvCache->get($cacheKey, function (ItemInterface $item) use ($body) {
  756.         $item->expiresAfter(self::EXPIRETIME_APEX);
  757.         $item->tag(self::CACHE_TAG_GETCODEAGENT);
  758.         $this->sf_auth->authenticate();
  759.         $url $this->sf_auth->getInstanceUrl() . '/services/apexrest/retrieveAgentCode/';
  760.         try {
  761.           $response $this->client->request('POST'$url, [
  762.               'timeout' => self::HTTPCLIENT_TIMEOUT,
  763.               'headers' => [
  764.                 'Authorization' => "Bearer " $this->sf_auth->getAccessToken(),
  765.               ],
  766.               'json' => $body
  767.           ]);
  768.           $content = [];
  769.           $content['url'] = $url;
  770.           $content['timestamp'] = time();
  771.           $content['response'] = $response->toArray();
  772.         }
  773.         catch(\Exception $e) {
  774.           if($e instanceof TransportException) {
  775.             $httpLogs = [/*$response->getInfo('debug')*/];
  776.             $content['response'] = $e->getMessage();
  777.             $this->logger->error('LMDV SF Api Error (not cached) retrieveAgentCode', ['debug' => $httpLogs'url' => $url'request' => '''response' => '']);
  778.             throw new WebServiceException($this->translator->trans('error.page.label_timeout'), LMDVException::ERROR_TIMEOUT);
  779.           }
  780.           if($response->getStatusCode() == 401) {
  781.             $this->sf_auth->invalidateAccessToken();
  782.           }
  783.           $httpLogs = [/*$response->getInfo('debug')*/];
  784.           $content['response'] = $e->getMessage();
  785.           $this->logger->error('LMDV Webservice Error (not cached) retrieveAgentCode', ['debug' => $httpLogs'url' => $url'request' => $body'response' => $content['response']]);
  786.           throw new WebServiceException($this->translator->trans("error.webservice.sf_api_error", ['message' => $response->getStatusCode()]), LMDVException::ERROR_SALESFORCE);
  787.         }
  788.         $content['httpClient']['debug'] = [/*$response->getInfo('debug')*/];
  789.         $content['httpClient']['request'] = $body;
  790.         return $content;
  791.       }, self::CACHE_BETA_EXPIRATION);
  792.       $cached '(not cached)';
  793.       if($cachedContent['timestamp'] < $timestamp) {
  794.         $cached '(cached)';
  795.       }
  796.       if(empty($cachedContent['response'])) {
  797.         $this->logger->info("LMDV Webservice Error $cached retrieveAgentCode", ['debug' => $cachedContent['httpClient']['debug'], 'url' => $cachedContent['url'], 'request' => $cachedContent['httpClient']['request'], 'response' => $cachedContent['response']]);
  798.         //$this->lmdvCache->delete($cacheKey);
  799.         throw new WebServiceException($this->translator->trans("error.webservice.sf_api_error", ['message' => 'agentCode is empty']), LMDVException::ERROR_SALESFORCE);
  800.       }
  801.       elseif(! isset($cachedContent['response']['Status'])) {
  802.         $this->logger->error("LMDV Webservice Error $cached retrieveAgentCode", ['debug' => $cachedContent['httpClient']['debug'], 'url' => $cachedContent['url'], 'request' => $cachedContent['httpClient']['request'], 'response' => $cachedContent['response']]);
  803.         //$this->lmdvCache->delete($cacheKey);
  804.         throw new WebServiceException($this->translator->trans("error.webservice.sf_api_error", ['message' =>"There is no status key"]), LMDVException::ERROR_SALESFORCE);
  805.       }
  806.       elseif($cachedContent['response']['Status'] == 'Error') {
  807.         $this->logger->error("LMDV Webservice Error $cached retrieveAgentCode", ['debug' => $cachedContent['httpClient']['debug'], 'url' => $cachedContent['url'], 'request' => $cachedContent['httpClient']['request'], 'response' => $cachedContent['response']]);
  808.         //$this->lmdvCache->delete($cacheKey);
  809.         throw new WebServiceException($this->translator->trans("error.webservice.sf_api_error", ['message' => $cachedContent['response']['message']]), LMDVException::ERROR_SALESFORCE);
  810.       }
  811.       $this->logger->info("LMDV Webservice Success $cached retrieveAgentCode", ['debug' => $cachedContent['httpClient']['debug'], 'url' => $cachedContent['url'], 'request' => $cachedContent['httpClient']['request'], 'response' => $cachedContent['response']]);
  812.       return $cachedContent['response'];
  813.   }
  814.   /**
  815.    * WS APEX getInfo : this WS is cached by user for saving SF API calls and to improve app performace.
  816.    *
  817.    * Note that SAPEIG error codes are returned through http 500 error messages
  818.    *
  819.    * @param VoyageInterface $voyage
  820.    * @throws WebServiceException
  821.    * @return array
  822.    */
  823.   public function getInfo(VoyageInterface $voyage) {
  824.       // If parameters are not valid, we skip making a call to the WS APEX, to avoid flooding the SF administrator
  825.       // with error emails if we know beforehand that the call will not be successfull. Testing for the productCode
  826.       // should be enough
  827.       if(! $voyage->getProductCode()) {
  828.         throw new WebServiceException($this->translator->trans("error.webservice.sf_api_error", ['message' => 'Session is empty or not valid, skipping call.']), LMDVException::ERROR_UNKNOWN);
  829.       }
  830.       $body = [];
  831.       $body['dto'] = [
  832.           'productCode' => $voyage->getProductCode(),
  833.           'departureDate' => $voyage->getDepartureDate(),
  834.           'returnDate' => $voyage->getEndDate(),
  835.           'departureCity' => $voyage->getDepartureCity(),
  836.           'adultNumber' => $voyage->getEtape0()->getNbrAdultes(),
  837.           'childNumber' => $voyage->getEtape0()->getNbrEnfants(),
  838.           'infantNumber' => "0",
  839.           'process' => $voyage->getProcess()->display()
  840.       ];
  841.       if($voyage->getIdOppSF()) {
  842.         $body['dto']['opportunityId'] = $voyage->getIdOppSF();
  843.       }
  844.       if($voyage->getIdOppSFParent()) {
  845.         $body['dto']['parentOpportunityId'] = $voyage->getIdOppSFParent();
  846.       }
  847.       if($voyage->getEtape0()->getCodeAgent()) {
  848.         $body['dto']['agentCode'] = $voyage->getEtape0()->getCodeAgent();
  849.       }
  850.       if($voyage->getEtape0()->getCodeSociete()) {
  851.         $body['dto']['requesterCode'] = $voyage->getEtape0()->getCodeSociete();
  852.       }
  853.       $cacheKey $body + [
  854.           'method' => 'getInfo',
  855.           'sessionId' => $voyage->getSessionId()
  856.       ];
  857.       $cacheKey md5(json_encode($cacheKey));
  858.       $timestamp time();
  859.       static $static_cache NULL;
  860.       $hasStatic FALSE;
  861.       if(! $static_cache) {
  862.           $cachedContent $this->lmdvCache->get($cacheKey, function (ItemInterface $item) use ($body$voyage) {
  863.             $item->expiresAfter(self::EXPIRETIME_APEX);
  864.             $item->tag(self::CACHE_TAG_GETINFO);
  865.             if($_ENV['WEBSERVICE_FAKE_DATA']) {
  866.               $code $voyage->getProductCode();
  867.               $data = \file_get_contents__DIR__ '/tests/'.$code.'/'.$code.'_getInfo_response.txt');
  868.               $data = \json_decode($dataTRUE);
  869.               $content = [];
  870.               $content['url'] = 'https://lmdv--partial-FAKE.my.salesforce.com' '/services/apexrest/getInfo/';
  871.               $content['timestamp'] = time();
  872.               $content['response'] = $data;
  873.               $content['httpClient']['debug'] = [/*$response->getInfo('debug')*/];
  874.               $content['httpClient']['request'] = $body;
  875.               return $content;
  876.             }
  877.             static $static_cache_errors NULL;
  878.             if($static_cache_errors) {
  879.               throw $static_cache_errors;
  880.             }
  881.             $this->sf_auth->authenticate();
  882.             $url $this->sf_auth->getInstanceUrl() . '/services/apexrest/getInfo/';
  883.             try {
  884.               $response $this->client->request('POST'$url, [
  885.                   'timeout' => self::HTTPCLIENT_TIMEOUT,
  886.                   'headers' => [
  887.                     'Authorization' => "Bearer " $this->sf_auth->getAccessToken(),
  888.                   ],
  889.                   'json' => $body
  890.               ]);
  891.               $content = [];
  892.               $content['url'] = $url;
  893.               $content['timestamp'] = time();
  894.               $content['response'] = $response->toArray(FALSE); // httpClient returns an Exception for http error codes 300-599 unless we passe FALSE here.
  895.             }
  896.             catch(\Exception $e) {
  897.               if($e instanceof TransportException) {
  898.                 $httpLogs = [/*$response->getInfo('debug')*/];
  899.                 $content['response'] = $e->getMessage();
  900.                 $this->logger->error('LMDV SF Api Error (not cached) getInfo', ['debug' => $httpLogs'url' => $url'request' => '''response' => '']);
  901.                 $static_cache_errors = new WebServiceException($this->translator->trans('error.page.label_timeout'), LMDVException::ERROR_TIMEOUT);
  902.                 throw $static_cache_errors;
  903.               }
  904.               if($response->getStatusCode() == 401) {
  905.                 $this->sf_auth->invalidateAccessToken();
  906.               }
  907.               $httpLogs = [/*$response->getInfo('debug')*/];
  908.               $content['response'] = $e->getMessage();
  909.               $this->logger->error('LMDV Webservice Error (not cached) getInfo', ['debug' => $httpLogs'url' => $url'request' => $body'response' => $content['response']]);
  910.               $static_cache_errors = new WebServiceException($this->translator->trans("error.webservice.sf_api_error", ['message' => $response->getStatusCode()]), LMDVException::ERROR_SALESFORCE);
  911.               throw $static_cache_errors;
  912.             }
  913.             $content['httpClient']['debug'] = [/*$response->getInfo('debug')*/];
  914.             $content['httpClient']['request'] = $body;
  915.             return $content;
  916.           }, self::CACHE_BETA_EXPIRATION);
  917.           $static_cache $cachedContent;
  918.       }
  919.       else {
  920.         $cachedContent $static_cache;
  921.         $hasStatic TRUE;
  922.       }
  923.       $cached '(not cached)';
  924.       if($cachedContent['timestamp'] < $timestamp) {
  925.         $cached '(cached)';
  926.       }
  927.       if(! isset($cachedContent['response']['Status'])) {
  928.         if(! $hasStatic) {
  929.           $this->logger->error("LMDV Webservice Error $cached getInfo", ['debug' => $cachedContent['httpClient']['debug'], 'url' => $cachedContent['url'], 'request' => $cachedContent['httpClient']['request'], 'response' => $cachedContent['response']]);
  930.         }
  931.         //$this->lmdvCache->delete($cacheKey);
  932.         throw new WebServiceException($this->translator->trans("error.webservice.sf_api_error", ['message' =>"There is no status key"]), LMDVException::ERROR_SALESFORCE);
  933.       }
  934.       elseif($cachedContent['response']['Status'] == 'Error') {
  935.         if(! $hasStatic) {
  936.           $this->logger->error("LMDV Webservice Error $cached getInfo", ['debug' => $cachedContent['httpClient']['debug'], 'url' => $cachedContent['url'], 'request' => $cachedContent['httpClient']['request'], 'response' => $cachedContent['response']]);
  937.         }
  938.         //$this->lmdvCache->delete($cacheKey);
  939.         if(strstr($cachedContent['response']['message'], 'Stock insuffisant') && $cachedContent['response']['errorType'] == 'SAPEIG') {
  940.           $e = new WebServiceException($this->translator->trans("error.webservice.sf_api_error", ['message' => $cachedContent['response']['message']]), LMDVException::ERROR_OUT_OF_STOCK);
  941.           $e->setData(['telephone' => $cachedContent['response']['supportPhone']]);
  942.           throw $e;
  943.         }
  944.         else {
  945.           throw new WebServiceException($this->translator->trans("error.webservice.sf_api_error", ['message' => $cachedContent['response']['message']]), $this->getErrorType($cachedContent['response']['errorType']));
  946.         }
  947.       }
  948.       if(! $hasStatic) {
  949.         $this->logger->info("LMDV Webservice Success $cached getInfo", ['debug' => $cachedContent['httpClient']['debug'], 'url' => $cachedContent['url'], 'request' => $cachedContent['httpClient']['request'], 'response' => $cachedContent['response']]);
  950.       }
  951.       // Adds session variables that are mapped into the Voyage object by SessionManager
  952.       if($cachedContent['response']['departure']['city']['text']) {
  953.         $this->request->getSession()->set('GETINFO_departureCity'$cachedContent['response']['departure']['city']['text']);
  954.       }
  955.       if($cachedContent['response']['destination']['country']['text']) {
  956.         $this->request->getSession()->set('GETINFO_country'$cachedContent['response']['destination']['country']['text']);
  957.       }
  958.       if($cachedContent['response']['title']) {
  959.         $this->request->getSession()->set('GETINFO_title'$cachedContent['response']['title']);
  960.       }
  961.       if($cachedContent['response']['supportPhone']) {
  962.         $this->request->getSession()->set('GETINFO_supportPhone'$cachedContent['response']['supportPhone']);
  963.       }
  964.       return $cachedContent['response'];
  965.   }
  966.   /**
  967.    * WS APEX getQA : this WS is cached by user for saving SF API calls and to improve app performace.
  968.    *
  969.    * Note that SAPEIG error codes are returned through http 500 error messages
  970.    *
  971.    * @param VoyageInterface $voyage
  972.    * @throws WebServiceException
  973.    * @return mixed
  974.    */
  975.   public function getQA(VoyageInterface $voyage) {
  976.     // If parameters are not valid, we skip making a call to the WS APEX, to avoid flooding the SF administrator
  977.     // with error emails if we know beforehand that the call will not be successfull. Testing for the productCode
  978.     // should be enough
  979.     if(! $voyage->getProductCode()) {
  980.       throw new WebServiceException($this->translator->trans("error.webservice.sf_api_error", ['message' => 'Session is empty or not valid, skipping call.']), LMDVException::ERROR_UNKNOWN);
  981.     }
  982.     $body = [];
  983.     $body['dto'] = [
  984.       'sessionCode' => $this->getSessionCodeGetInfo($voyage),
  985.       'productCode' => $voyage->getProductCode(),
  986.       'airSegmentsCodes' => $this->getAirSegmentsCodes($voyage),
  987.       'staySegments' => $this->getStaySegments($voyage),
  988.       'miscellaneousSegments' => $this->getMiscellaneousSegments($voyage),
  989.       //'insuranceSegments' => $this->getInsurancesSegments($voyage),
  990.       'departureDate' => $voyage->getDepartureDate(),
  991.       'endDate' => $voyage->getEndDate(),
  992.       'adultNumber' => $voyage->getEtape0()->getNbrAdultes(),
  993.       'childNumber' => $voyage->getEtape0()->getNbrEnfants(),
  994.       'infantNumber' => "0",
  995.       'departureCity' => $voyage->getDepartureCity(),
  996.       'process' => $voyage->getProcess()->display()
  997.     ];
  998.     if($voyage->getEtape0()->getCodeAgent()) {
  999.       $body['dto']['agentCode'] = $voyage->getEtape0()->getCodeAgent();
  1000.     }
  1001.     if($voyage->getEtape0()->getCodeSociete()) {
  1002.       $body['dto']['requesterCode'] = $voyage->getEtape0()->getCodeSociete();
  1003.     }
  1004.     if($voyage->getIdOppSF()) {
  1005.       $body['dto']['opportunityId'] = $voyage->getIdOppSF();
  1006.     }
  1007.     if($voyage->getIdOppSFParent()) {
  1008.       $body['dto']['parentOpportunityId'] = $voyage->getIdOppSFParent();
  1009.     }
  1010.     $cacheKey $body + [
  1011.         'method' => 'getQA',
  1012.         'sessionId' => $voyage->getSessionId()
  1013.     ];
  1014.     $cacheKey md5(json_encode($cacheKey));
  1015.     $timestamp time();
  1016.     static $static_cache NULL;
  1017.     $hasStatic FALSE;
  1018.     if(! $static_cache) {
  1019.         $cachedContent $this->lmdvCache->get($cacheKey, function (ItemInterface $item) use ($body$voyage) {
  1020.           $item->expiresAfter(self::EXPIRETIME_APEX);
  1021.           $item->tag(self::CACHE_TAG_GETQA);
  1022.           if($_ENV['WEBSERVICE_FAKE_DATA']) {
  1023.             $code $voyage->getProductCode();
  1024.             $data = \file_get_contents__DIR__ '/tests/'.$code.'/'.$code.'_getQA_response.txt');
  1025.             $data = \json_decode($dataTRUE);
  1026.             $content = [];
  1027.             $content['url'] = 'https://lmdv--partial-FAKE.my.salesforce.com' '/services/apexrest/getQA/';
  1028.             $content['timestamp'] = time();
  1029.             $content['response'] = $data;
  1030.             $content['httpClient']['debug'] = [/*$response->getInfo('debug')*/];
  1031.             $content['httpClient']['request'] = $body;
  1032.             return $content;
  1033.           }
  1034.           static $static_cache_errors NULL;
  1035.           if($static_cache_errors) {
  1036.             throw $static_cache_errors;
  1037.           }
  1038.           $this->sf_auth->authenticate();
  1039.           $url $this->sf_auth->getInstanceUrl() . '/services/apexrest/getQA/';
  1040.           try {
  1041.             $response $this->client->request('POST'$url, [
  1042.                 'timeout' => self::HTTPCLIENT_TIMEOUT,
  1043.                 'headers' => [
  1044.                   'Authorization' => "Bearer " $this->sf_auth->getAccessToken(),
  1045.                 ],
  1046.                 'json' => $body
  1047.             ]);
  1048.             $content = [];
  1049.             $content['url'] = $url;
  1050.             $content['timestamp'] = time();
  1051.             $content['response'] = $response->toArray(FALSE); // httpClient returns an Exception for http error codes 300-599 unless we passe FALSE here.
  1052.           }
  1053.           catch(\Exception $e) {
  1054.             if($e instanceof TransportException) {
  1055.               $httpLogs = [/*$response->getInfo('debug')*/];
  1056.               $content['response'] = $e->getMessage();
  1057.               $this->logger->error('LMDV SF Api Error (not cached) getQA', ['debug' => $httpLogs'url' => $url'request' => '''response' => '']);
  1058.               $static_cache_errors = new WebServiceException($this->translator->trans('error.page.label_timeout'), LMDVException::ERROR_TIMEOUT);
  1059.               throw $static_cache_errors;
  1060.             }
  1061.             if($response->getStatusCode() == 401) {
  1062.               $this->sf_auth->invalidateAccessToken();
  1063.             }
  1064.             $httpLogs = [/*$response->getInfo('debug')*/];
  1065.             $content['response'] = $e->getMessage();
  1066.             $this->logger->error('LMDV Webservice Error (not cached) getQA', ['debug' => $httpLogs'url' => $url'request' => $body'response' => $content['response']]);
  1067.             $static_cache_errors = new WebServiceException($this->translator->trans("error.webservice.sf_api_error", ['message' => $response->getStatusCode()]), LMDVException::ERROR_SALESFORCE);
  1068.             throw $static_cache_errors;
  1069.           }
  1070.           $content['httpClient']['debug'] = [/*$response->getInfo('debug')*/];
  1071.           $content['httpClient']['request'] = $body;
  1072.           return $content;
  1073.         }, self::CACHE_BETA_EXPIRATION);
  1074.         $static_cache $cachedContent;
  1075.     }
  1076.     else {
  1077.       $cachedContent $static_cache;
  1078.       $hasStatic TRUE;
  1079.     }
  1080.     $cached '(not cached)';
  1081.     if($cachedContent['timestamp'] < $timestamp) {
  1082.       $cached '(cached)';
  1083.     }
  1084.     if(! isset($cachedContent['response']['Status'])) {
  1085.       if(! $hasStatic) {
  1086.         $this->logger->error("LMDV Webservice Error $cached getQA", ['debug' => $cachedContent['httpClient']['debug'], 'url' => $cachedContent['url'], 'request' => $cachedContent['httpClient']['request'], 'response' => $cachedContent['response']]);
  1087.       }
  1088.       //$this->lmdvCache->delete($cacheKey);
  1089.       throw new WebServiceException($this->translator->trans("error.webservice.sf_api_error", ['message' => "There is no status key"]), LMDVException::ERROR_SALESFORCE);
  1090.     }
  1091.     elseif($cachedContent['response']['Status'] == 'Error') {
  1092.       if(! $hasStatic) {
  1093.         $this->logger->error("LMDV Webservice Error $cached getQA", ['debug' => $cachedContent['httpClient']['debug'], 'url' => $cachedContent['url'], 'request' => $cachedContent['httpClient']['request'], 'response' => $cachedContent['response']]);
  1094.       }
  1095.       //$this->lmdvCache->delete($cacheKey);
  1096.       throw new WebServiceException($this->translator->trans("error.webservice.sf_api_error", ['message' => $cachedContent['response']['message']]), $this->getErrorType($cachedContent['response']['errorType']));
  1097.     }
  1098.     if(! $hasStatic) {
  1099.       $this->logger->info("LMDV Webservice Success $cached getQA", ['debug' => $cachedContent['httpClient']['debug'], 'url' => $cachedContent['url'], 'request' => $cachedContent['httpClient']['request'], 'response' => $cachedContent['response']]);
  1100.     }
  1101.     return $cachedContent['response'];
  1102.   }
  1103.   /**
  1104.    * WS APEX createBooking : we use a cache for this webservice to avoid the user calling multiple times createBooking,
  1105.    * which can happen if the user submits the form multiple times using CTRL+R (while waiting for a response), or when
  1106.    * the client goes to the HiPay page and hits the browser back button without doing any paiement.
  1107.    *
  1108.    * Note that SAPEIG error codes are returned through http 500 error messages
  1109.    *
  1110.    * @param VoyageInterface $voyage
  1111.    * @throws WebServiceException
  1112.    * @return array
  1113.    */
  1114.   public function createBooking(VoyageInterface $voyage) {
  1115.     $body = [];
  1116.     $body['dto'] = [
  1117.       'productCode' => $voyage->getProductCode(),
  1118.       'sessionCode' => $this->getSessionCodeGetQA($voyage),
  1119.       'airSegmentsCodes' => $this->getAirSegmentsCodes($voyage),
  1120.       'staySegments' => $this->getStaySegments($voyage),
  1121.       'miscellaneousSegments' => $this->getMiscellaneousSegments($voyage),
  1122.       //'insuranceSegments' => $this->getInsurancesSegments($voyage),
  1123.       "travellers" => [], // <- will be filled up below
  1124.       'departureDate' => $voyage->getDepartureDate(),
  1125.       'departureCity' => $voyage->getDepartureCity(),
  1126.       'endDate' => $voyage->getEndDate(),
  1127.       'process' => $voyage->getProcess()->display(),
  1128.       'paymentType' => $voyage->getEtape3()->getTypePayment() == Etape3::PAYMENT_TYPE_ONLINE 'Paiement en ligne' 'Paiement en agence'
  1129.     ];
  1130.     // Sends PDF contrat de vent to SF encoded in base64
  1131.     $pdfContent '';
  1132.     try {
  1133.       $pdfContent $this->print->print($voyage);
  1134.       $pdfContent = \base64_encode($pdfContent);
  1135.     }
  1136.     catch(\Exception $e) {
  1137.     }
  1138.     if($pdfContent) {
  1139.       $body['dto']['pdfContent'] = $pdfContent;
  1140.     }
  1141.     if($voyage->getEtape2()->getCivilite()) {
  1142.       $body['dto']['customer'] = [
  1143.           'title' => $voyage->getEtape2()->getCivilite() == 'Monsieur' 'MR' 'MRS',
  1144.           'firstName' => $voyage->getEtape2()->getPrenom(),
  1145.           'lastName' => $voyage->getEtape2()->getNom(),
  1146.           'street' => $voyage->getEtape2()->getAdresse(),
  1147.           'zipCode' => $voyage->getEtape2()->getCodePostal(),
  1148.           'city' => $voyage->getEtape2()->getVille(),
  1149.           'country' => $voyage->getEtape2()->getPays(),
  1150.           'phone' => $voyage->getEtape2()->getTelephone(),
  1151.           'secondPhone' => $voyage->getEtape2()->getTelephone2(),
  1152.           'email' => $voyage->getEtape2()->getEmail(),
  1153.       ];
  1154.     }
  1155.     // Ticket LMDV-507 : Envoyer les valeurs UTM et Referrer dans l'opportunité
  1156.     $body['dto']['utm'] = [
  1157.       'source' => $this->request->cookies->get('_uc_utm_source') ?? '',
  1158.       'medium' => $this->request->cookies->get('_uc_utm_medium') ?? '',
  1159.       'campaign' => $this->request->cookies->get('_uc_utm_campaign') ?? '',
  1160.       'content' => $this->request->cookies->get('_uc_utm_content') ?? '',
  1161.       'term' => $this->request->cookies->get('_uc_utm_term') ?? '',
  1162.       'referer' => $this->request->cookies->get('_uc_referrer') ?? '',
  1163.     ];
  1164.     if($voyage->getEtape2()->getCommentaires()) {
  1165.       $body['dto']['comment'] = $voyage->getEtape2()->getCommentaires();
  1166.     }
  1167.     // Par rapport aux Ids, s'ils sont renseignés, on est dans un cas de mise Ã  jour et sinon c'est une création dans Salesforce
  1168.     if($voyage->getIdOppSF()) {
  1169.       $body['dto']['opportunityId'] = $voyage->getIdOppSF();
  1170.     }
  1171.     if($voyage->getIdOppSF() && $voyage->getEtape2()->getSfId()) {
  1172.       $body['dto']['customer']['sfId'] = $voyage->getEtape2()->getSfId();
  1173.     }
  1174.     if($voyage->getEtape0()->getCodeSociete()) {
  1175.       $body['dto']['requesterCode'] = $voyage->getEtape0()->getCodeSociete();
  1176.     }
  1177.     if($voyage->getEtape0()->getCodeAgent()) {
  1178.       $body['dto']['agentCode'] = $voyage->getEtape0()->getCodeAgent();
  1179.     }
  1180.     // This is for GRPFactIndivWeb, we must link the new SFP opp with its PARENT.
  1181.     if($voyage->getIdOppSFParent() && ! $voyage->getIdOppSF()) {
  1182.       $body['dto']['parentOpportunityId'] = $voyage->getIdOppSFParent();
  1183.     }
  1184.     // This is for GIRDistrib, we have to link the agentCode previously created with the new SF opp.
  1185.     if($voyage->getEtape0()->getCodeAgent() && $voyage->getEtape0()->getMailVendeur()) {
  1186.       $body['dto']['agentEmail'] = $voyage->getEtape0()->getMailVendeur();
  1187.     }
  1188.     // Fills up travellers information
  1189.     foreach($voyage->getEtape2()->getParticipants() as $participant) {
  1190.       if($participant->isChild()) {
  1191.         $type 'Child';
  1192.       }
  1193.       else {
  1194.         $type 'Adult';
  1195.       }
  1196.       // Some forms use a CountryType others just a textfield
  1197.       $pays $participant->getNationalite();
  1198.       try {
  1199.         $pays = \Symfony\Component\Intl\Countries::getName($participant->getNationalite());
  1200.       }
  1201.       catch(\Exception $e) {
  1202.          $this->logger->error('Country name not found for country code "' $participant->getNationalite() . '"');
  1203.       }
  1204.       $aux = [
  1205.           'title' => $participant->getCivilite() == 'Monsieur' 'MR' 'MRS',
  1206.           'firstName' => $participant->getPrenom(),
  1207.           'lastName' => $participant->getNom(),
  1208.           'type' => $type,
  1209.           'birthDate' => date('Y-m-d'$this->getTimestampFromDate($participant->getDateNaissance())),
  1210.           'Nationality' => $pays,
  1211.       ];
  1212.       // Il est possible aussi de renseigner un Id Salesforce de Pax__c si tu veux mettre Ã  jour un voyageur dans Salesforce
  1213.       if($voyage->getIdOppSF() && $participant->getSfId()) {
  1214.         $aux['sfId'] = $participant->getSfId();
  1215.       }
  1216.       if($participant->getCivilite() == 'Madame') {
  1217.         $aux['maidenName'] = $participant->getNomJeuneFille();
  1218.       }
  1219.       $body['dto']['travellers'][] = $aux;
  1220.     }
  1221.     $cacheKey $body + [
  1222.         'method' => 'createBooking',
  1223.         'sessionId' => $voyage->getSessionId()
  1224.     ];
  1225.     // We do not take into account these two keys for computing the key.
  1226.     unset($cacheKey['dto']['paymentType']);
  1227.     unset($cacheKey['dto']['pdfContent']);
  1228.     $cacheKey md5(json_encode($cacheKey));
  1229.     $timestamp time();
  1230.     $cachedContent $this->lmdvCache->get($cacheKey, function (ItemInterface $item) use ($body$voyage) {
  1231.       $item->expiresAfter(self::EXPIRETIME_APEX);
  1232.       $item->tag(self::CACHE_TAG_CREATEBOOKING);
  1233.       if($_ENV['WEBSERVICE_FAKE_DATA']) {
  1234.         $code $voyage->getProductCode();
  1235.         $data = \file_get_contents__DIR__ '/tests/'.$code.'/'.$code.'_createBooking_response.txt');
  1236.         $data = \json_decode($dataTRUE);
  1237.         // Because createBooking is extremely slow, we simulate the same behaviour with a PHP sleep.
  1238.         sleep(30);
  1239.         $content = [];
  1240.         $content['url'] = 'https://lmdv--partial-FAKE.my.salesforce.com' '/services/apexrest/createBooking/';
  1241.         $content['timestamp'] = time();
  1242.         $content['response'] = $data;
  1243.         $content['httpClient']['debug'] = [/*$response->getInfo('debug')*/];
  1244.         $content['httpClient']['request'] = $body;
  1245.         return $content;
  1246.       }
  1247.       $this->sf_auth->authenticate();
  1248.       $url $this->sf_auth->getInstanceUrl() . '/services/apexrest/createBooking/';
  1249.       try {
  1250.         $response $this->client->request('POST'$url, [
  1251.             'timeout' => self::HTTPCLIENT_TIMEOUT,
  1252.             'headers' => [
  1253.               'Authorization' => "Bearer " $this->sf_auth->getAccessToken(),
  1254.             ],
  1255.             'json' => $body
  1256.         ]);
  1257.         $content = [];
  1258.         $content['url'] = $url;
  1259.         $content['timestamp'] = time();
  1260.         $content['response'] = $response->toArray(FALSE); // httpClient returns an Exception for http error codes 300-599 unless we passe FALSE here.
  1261.       }
  1262.       catch(\Exception $e) {
  1263.         if($e instanceof TransportException) {
  1264.           $httpLogs = [/*$response->getInfo('debug')*/];
  1265.           $content['response'] = $e->getMessage();
  1266.           $this->logger->error('LMDV Webservice Error createBooking', ['debug' => $httpLogs'url' => $url'request' => $body'response' => '']);
  1267.           throw new WebServiceException($this->translator->trans('error.page.label_timeout_createBooking'), LMDVException::ERROR_TIMEOUT_CREATEBOOKING);
  1268.         }
  1269.         if($response->getStatusCode() == 401) {
  1270.           $this->sf_auth->invalidateAccessToken();
  1271.         }
  1272.         $httpLogs = [/*$response->getInfo('debug')*/];
  1273.         $content['response'] = $e->getMessage();
  1274.         $this->logger->error('LMDV Webservice Error createBooking', ['debug' => $httpLogs'url' => $url'request' => $body'response' => $content['response']]);
  1275.         throw new WebServiceException($this->translator->trans("error.webservice.sf_api_error", ['message' => $response->getStatusCode()]), LMDVException::ERROR_SALESFORCE_CREATEBOOKING);
  1276.       }
  1277.       $content['httpClient']['debug'] = [/*$response->getInfo('debug')*/];
  1278.       $content['httpClient']['request'] = $body;
  1279.       return $content;
  1280.     }, self::CACHE_BETA_EXPIRATION);
  1281.     $cached '(not cached)';
  1282.     if($cachedContent['timestamp'] < $timestamp) {
  1283.       $cached '(cached)';
  1284.     }
  1285.     if(! isset($cachedContent['response']['Status'])) {
  1286.       $this->logger->error("LMDV Webservice Error $cached createBooking", ['debug' => $cachedContent['httpClient']['debug'], 'url' => $cachedContent['url'], 'request' => $cachedContent['httpClient']['request'], 'response' => $cachedContent['response']]);
  1287.       $this->lmdvCache->delete($cacheKey);
  1288.       throw new WebServiceException($this->translator->trans("error.webservice.sf_api_error", ['message' => "There is no status key"]), LMDVException::ERROR_SALESFORCE_CREATEBOOKING);
  1289.     }
  1290.     elseif($cachedContent['response']['Status'] == 'Error') {
  1291.       $this->logger->error("LMDV Webservice Error $cached createBooking", ['debug' => $cachedContent['httpClient']['debug'], 'url' => $cachedContent['url'], 'request' => $cachedContent['httpClient']['request'], 'response' => $cachedContent['response']]);
  1292.       $this->lmdvCache->delete($cacheKey);
  1293.       throw new WebServiceException($this->translator->trans("error.webservice.sf_api_error", ['message' => $cachedContent['response']['message']]), $this->getErrorTypeCreateBooking($cachedContent['response']['errorType']));
  1294.     }
  1295.     // Removes pdfContent from the log INFO to avoid pollution
  1296.     unset($cachedContent['httpClient']['request']['dto']['pdfContent']);
  1297.     $this->logger->info("LMDV Webservice Success $cached createBooking", ['debug' => $cachedContent['httpClient']['debug'], 'url' => $cachedContent['url'], 'request' => $cachedContent['httpClient']['request'], 'response' => $cachedContent['response']]);
  1298.     return $cachedContent['response'];
  1299.   }
  1300.   /**
  1301.    * WS APEX addPayment
  1302.    *
  1303.    * Note that SAPEIG error codes are returned through http 500 error messages
  1304.    *
  1305.    * @param VoyageInterface $voyage
  1306.    * @throws WebServiceException
  1307.    * @return array
  1308.    */
  1309.   public function addPayment(PaymentEventInterface $event) {
  1310.     if($_ENV['WEBSERVICE_FAKE_DATA']) {
  1311.       $result = [
  1312.         'Status' => 'Success'
  1313.       ];
  1314.       return $result;
  1315.     }
  1316.     $this->sf_auth->authenticate();
  1317.     $url $this->sf_auth->getInstanceUrl() . '/services/apexrest/AddPayment/';
  1318.     $voyage $event->getVoyage();
  1319.     $transaction $event->getTransaction();
  1320.     $body = [];
  1321.     $body['dto'] = [
  1322.       "process" => $voyage->getProcess()->display(),
  1323.       "status" => $event->getType() == HiPayManager::HIPAY_CALLBACK_ACCEPT 'OK' 'REFUSED',
  1324.       "reason" => $transaction['reason'],
  1325.       "bookingNumber" => $voyage->getEtape3()->getBookingNumber(),
  1326.       "opportunityId" => $voyage->getEtape3()->getNewIdOpp(),
  1327.       "payment" => [
  1328.         "paymentDate" => date('c'$transaction['date']),
  1329.         "reference" => $transaction['reference'],
  1330.         "amount" => $transaction['amount'] * 100// Amount must be sent in centimes
  1331.         "code" => 'CB',
  1332.         "payer" => $voyage->getEtape2()->getNom() . ' ' $voyage->getEtape2()->getPrenom(),
  1333.       ]
  1334.     ];
  1335.     if($voyage->getEtape0()->getCodeSociete()) {
  1336.       $body['dto']['requesterCode'] = $voyage->getEtape0()->getCodeSociete();
  1337.     }
  1338.     try {
  1339.       $response $this->client->request('POST'$url, [
  1340.           'timeout' => self::HTTPCLIENT_TIMEOUT,
  1341.           'headers' => [
  1342.             'Authorization' => "Bearer " $this->sf_auth->getAccessToken(),
  1343.           ],
  1344.           'json' => $body
  1345.       ]);
  1346.       $content = [];
  1347.       $content['response'] = $response->toArray(FALSE); // httpClient returns an Exception for http error codes 300-599 unless we passe FALSE here.
  1348.     }
  1349.     catch(\Exception $e) {
  1350.       if($e instanceof TransportException) {
  1351.         $httpLogs = [/*$response->getInfo('debug')*/];
  1352.         $content['response'] = $e->getMessage();
  1353.         $this->logger->error('LMDV Webservice Error AddPayment', ['debug' => $httpLogs'url' => $url'request' => $body'response' => '']);
  1354.         throw new WebServiceException($this->translator->trans('error.page.label_timeout'), LMDVException::ERROR_TIMEOUT);
  1355.       }
  1356.       if($response->getStatusCode() == 401) {
  1357.         $this->sf_auth->invalidateAccessToken();
  1358.       }
  1359.       $httpLogs = [/*$response->getInfo('debug')*/];
  1360.       $content['response'] = $e->getMessage();
  1361.       $this->logger->error('LMDV Webservice Error AddPayment', ['debug' => $httpLogs'url' => $url'request' => $body'response' => $content['response']]);
  1362.       throw new WebServiceException($this->translator->trans("error.webservice.sf_api_error", ['message' => $response->getStatusCode()]), LMDVException::ERROR_SALESFORCE);
  1363.     }
  1364.     $content['httpClient']['debug'] = [/*$response->getInfo('debug')*/];
  1365.     $content['httpClient']['request'] = $body;
  1366.     if(! isset($content['response']['Status'])) {
  1367.       $this->logger->error('LMDV Webservice Error AddPayment', ['debug' => $content['httpClient']['debug'], 'url' => $url'request' => $content['httpClient']['request'], 'response' => $content['response']]);
  1368.       throw new WebServiceException($this->translator->trans("error.webservice.sf_api_error", ['message' => "There is no status key"]), LMDVException::ERROR_SALESFORCE);
  1369.     }
  1370.     elseif($content['response']['Status'] == 'Error') {
  1371.       $this->logger->error('LMDV Webservice Error AddPayment', ['debug' => $content['httpClient']['debug'], 'url' => $url'request' => $content['httpClient']['request'], 'response' => $content['response']]);
  1372.       throw new WebServiceException($this->translator->trans("error.webservice.sf_api_error", ['message' => $content['response']['message']]), LMDVException::ERROR_SALESFORCE);
  1373.     }
  1374.     $this->logger->info('LMDV Webservice Success AddPayment', ['debug' => $content['httpClient']['debug'], 'url' => $url'request' => $content['httpClient']['request'], 'response' => $content['response']]);
  1375.     return $content['response'];
  1376.   }
  1377.   /**
  1378.    * This is not used at the moment but it shows the way to invalidate
  1379.    * specific caches if necessary.
  1380.    *
  1381.    * @param array $tags
  1382.    */
  1383.   public function invalidateCache(array $tags) {
  1384.     $this->lmdvCache->invalidateTags($tags);
  1385.   }
  1386.   /**
  1387.    * This is not used at the moment but it shows the way to invalidate all
  1388.    * caches if necessary
  1389.    */
  1390.   public function invalidateAllCaches() {
  1391.     $tags = [
  1392.       self::CACHE_TAG_GETINFO,
  1393.       self::CACHE_TAG_GETQA,
  1394.       self::CACHE_TAG_CREATEBOOKING,
  1395.       self::CACHE_TAG_GETPDF,
  1396.       self::CACHE_TAG_GETCODEAGENT,
  1397.       self::CACHE_TAG_GETSALESFORCEDATA,
  1398.       self::CACHE_TAG_GETSALESFORCEPARTICIPANTS,
  1399.       self::CACHE_TAG_GETSALESFORCERESPONSABLE,
  1400.       self::CACHE_TAG_GETSALESFORCETITLEVOYAGE
  1401.     ];
  1402.     $this->lmdvCache->invalidateTags($tags);
  1403.   }
  1404.   /**
  1405.    * Expects a date as "d/m/Y" and return its timestamp.
  1406.    *
  1407.    * @param string $date
  1408.    *    A date in the format "d/m/Y"
  1409.    * @return int
  1410.    *    The timestamp
  1411.    */
  1412.   protected function getTimestampFromDate($date) {
  1413.     $date str_replace('/''-'$date);
  1414.     return strtotime($date);
  1415.   }
  1416.   /**
  1417.    * Gets a list of "stay segments" from getInfo(), which we must send later when calling
  1418.    * getQA/createBooking. Note that "staySegments" must be grouped by "segmentCode", i.e if there are
  1419.    * two rooms of the same type, instead of sending :
  1420.    *
  1421.    *   "staySegments" => [â–¼
  1422.    *     [â–¼
  1423.    *       "segmentCode" => "3"
  1424.    *       "rooms" => [â–¼
  1425.    *         [â–¼
  1426.    *           "roomCode" => "IND"
  1427.    *           "adultNumber" => 1
  1428.    *           "childNumber" => 0
  1429.    *           "infantNumber" => "0"
  1430.    *         ]
  1431.    *       ]
  1432.    *     ]
  1433.    *     [â–¼
  1434.    *       "segmentCode" => "3"
  1435.    *       "rooms" => [â–¼
  1436.    *         [â–¼
  1437.    *           "roomCode" => "IND"
  1438.    *           "adultNumber" => 1
  1439.    *           "childNumber" => 0
  1440.    *           "infantNumber" => "0"
  1441.    *         ]
  1442.    *       ]
  1443.    *     ]
  1444.    *   ]
  1445.    *
  1446.    * we must send :
  1447.    *
  1448.    *   "staySegments" => [â–¼
  1449.    *     [â–¼
  1450.    *       "segmentCode" => "3"
  1451.    *       "rooms" => [â–¼
  1452.    *         [â–¼
  1453.    *           "roomCode" => "IND"
  1454.    *           "adultNumber" => 1
  1455.    *           "childNumber" => 0
  1456.    *           "infantNumber" => "0"
  1457.    *         ],
  1458.    *         [â–¼
  1459.    *           "roomCode" => "IND"
  1460.    *           "adultNumber" => 1
  1461.    *           "childNumber" => 0
  1462.    *           "infantNumber" => "0"
  1463.    *         ]
  1464.    *       ]
  1465.    *     ]
  1466.    *   ]
  1467.    *
  1468.    * @todo: maybe use a ValueObject instead of an array.
  1469.    *
  1470.    * @param VoyageInterface $voyage
  1471.    * @return array
  1472.    */
  1473.   protected function getStaySegments(VoyageInterface $voyage) {
  1474.     $staySegments = [];
  1475.     foreach($voyage->getEtape1()->getChambres() as $chambre) {
  1476.       $rooms = [
  1477.         'roomCode' => $chambre->getCode(),
  1478.         'adultNumber' => $chambre->getNbrAdultes(),
  1479.         'childNumber' => $chambre->getNbrEnfants(),
  1480.         'infantNumber' => "0",
  1481.       ];
  1482.       $segmentCode $chambre->getSegmentCode();
  1483.       if(! isset($staySegments[$segmentCode])) {
  1484.         $staySegments[$segmentCode] = ['segmentCode' => $segmentCode];
  1485.       }
  1486.       $staySegments[$segmentCode]['rooms'][] = $rooms;
  1487.     }
  1488.     return array_values($staySegments);
  1489.   }
  1490.   /**
  1491.    * The sessionCode of getInfo() must be used for calls to getQA()
  1492.    */
  1493.   protected function getSessionCodeGetInfo(VoyageInterface $voyage) {
  1494.     $data $this->getInfo($voyage);
  1495.     return $data['control']['Session'];
  1496.   }
  1497.   /**
  1498.    * The sessionCode of getQA() must be used for calls to createBooking()
  1499.    */
  1500.   protected function getSessionCodeGetQA(VoyageInterface $voyage) {
  1501.     $data $this->getQA($voyage);
  1502.     return $data['control']['Session'];
  1503.   }
  1504.   /**
  1505.    * Gets a list of "air segments codes" from getInfo(), which we must send later when calling
  1506.    * getQA/createBooking
  1507.    *
  1508.    * @todo : maybe use a ValueObject instead of an array
  1509.    *
  1510.    * @param VoyageInterface $voyage
  1511.    * @return array
  1512.    */
  1513.   protected function getAirSegmentsCodes(VoyageInterface $voyage) {
  1514.     $data $this->getInfo($voyage);
  1515.     $segmentCodes = [];
  1516.     foreach($data['structuredFlight']['list'] as $l) {
  1517.       foreach($l['list'] as $l2) {
  1518.         foreach($l2['codes'] as $c) {
  1519.           if($c['name'] == 'INTID') {
  1520.             $segmentCodes[] = $c['value'];
  1521.           }
  1522.         }
  1523.       }
  1524.     }
  1525.     return $segmentCodes;
  1526.   }
  1527.   /**
  1528.    * Gets a list of "miscellaneous segments" from getInfo(), which we must send later when calling
  1529.    * getQA/createBooking.
  1530.    *
  1531.    * This function merges the data from "assurances", "prestations optionnelles" et "prestations
  1532.    * facultatives (visas)" into a single request key called "miscellaneousSegments".
  1533.    *
  1534.    * @todo: maybe use a ValueObject instead of an array.
  1535.    *
  1536.    * @param VoyageInterface $voyage
  1537.    * @return array
  1538.    */
  1539.   protected function getMiscellaneousSegments(VoyageInterface $voyage) {
  1540.     $data $this->getInfo($voyage);
  1541.     $miscSegments = [];
  1542.     // "prestations facultatives (visas)"
  1543.     if(is_array($data['miscellaneous'])) {
  1544.       foreach($data['miscellaneous'] as $m) {
  1545.         foreach($m['codes'] as $c) {
  1546.           if($c['name'] == 'INTID') {
  1547.             $miscCode $c['value'];
  1548.           }
  1549.         }
  1550.         foreach($m['segments'] as $s) {
  1551.           $segmentCode $s['code']['value'];
  1552.         }
  1553.         $miscSegments[] = ['miscellaneousCode' => $miscCode'segmentCode' => $segmentCode'quantity' => $voyage->getEtape0()->getNbrAdultes() + $voyage->getEtape0()->getNbrEnfants()];
  1554.       }
  1555.     }
  1556.     // "prestations optionnelles"
  1557.     if(is_array($data['optional'])) {
  1558.       foreach($data['optional'] as $m) {
  1559.         foreach($m['codes'] as $c) {
  1560.           if($c['name'] == 'INTID') {
  1561.             $miscCode $c['value'];
  1562.           }
  1563.         }
  1564.         foreach($m['segments'] as $s) {
  1565.           $segmentCode $s['code']['value'];
  1566.         }
  1567.         $miscSegments[] = ['miscellaneousCode' => $miscCode'segmentCode' => $segmentCode'quantity' => $voyage->getEtape0()->getNbrAdultes() + $voyage->getEtape0()->getNbrEnfants()];
  1568.       }
  1569.     }
  1570.     // "assurances"
  1571.     if(is_array($data['insurances'])) {
  1572.       foreach($data['insurances'] as $m) {
  1573.         foreach($m['codes'] as $c) {
  1574.           if($c['name'] == 'INTID') {
  1575.             $miscCode $c['value'];
  1576.           }
  1577.         }
  1578.         foreach($m['segments'] as $s) {
  1579.           $segmentCode $s['code']['value'];
  1580.         }
  1581.         $miscSegments[] = ['miscellaneousCode' => $miscCode'segmentCode' => $segmentCode'quantity' => $voyage->getEtape0()->getNbrAdultes() + $voyage->getEtape0()->getNbrEnfants()];
  1582.       }
  1583.     }
  1584.     // Filter available options by options chosen by the user
  1585.     $miscSegmentsFiltered = [];
  1586.     $choices1 $voyage->getEtape1()->getAssurances();
  1587.     $choices2 $voyage->getEtape1()->getPrestations();
  1588.     $choices array_merge($choices1$choices2);
  1589.     if(! empty($choices)) {
  1590.       foreach($choices as $key) {
  1591.         list($assurance_name$segmentCode$miscCode) = explode('#'$key);
  1592.         foreach($miscSegments as $m) {
  1593.           if($segmentCode == $m['segmentCode'] && $miscCode == $m['miscellaneousCode']) {
  1594.             $miscSegmentsFiltered[] = $m;
  1595.           }
  1596.         }
  1597.       }
  1598.     }
  1599.     return $miscSegmentsFiltered;
  1600.   }
  1601.   /**
  1602.    * Gets a list of "insurance segments" from getInfo(), which we must send later when calling
  1603.    * getQA/createBooking
  1604.    *
  1605.    * NOTE: Not use at the moment, because SAPEIG needs both "miscellaneous" and "insurances" keys inside "miscellaneousSegments"
  1606.    *
  1607.    * @param VoyageInterface $voyage
  1608.    * @return array
  1609.    */
  1610.   protected function getInsurancesSegments(VoyageInterface $voyage) {
  1611.     $data $this->getInfo($voyage);
  1612.     $miscSegments = [];
  1613.     if(is_array($data['insurances'])) {
  1614.       foreach($data['insurances'] as $m) {
  1615.         foreach($m['codes'] as $c) {
  1616.           if($c['name'] == 'INTID') {
  1617.             $miscCode $c['value'];
  1618.           }
  1619.         }
  1620.         foreach($m['segments'] as $s) {
  1621.           $segmentCode $s['code']['value'];
  1622.         }
  1623.         $miscSegments[] = ['miscellaneousCode' => $miscCode'segmentCode' => $segmentCode'quantity' => 1];
  1624.       }
  1625.     }
  1626.     // Filter available options by options chosen by the user
  1627.     $miscSegmentsFiltered = [];
  1628.     $selection $voyage->getEtape1()->getAssurances();
  1629.     if(! empty($selection)) {
  1630.       foreach($selection as $key) {
  1631.         list($assurance_name$segmentCode$miscCode) = explode('#'$key);
  1632.         foreach($miscSegments as $m) {
  1633.           if($segmentCode == $m['segmentCode'] && $miscCode == $m['miscellaneousCode']) {
  1634.             $miscSegmentsFiltered[] = $m;
  1635.           }
  1636.         }
  1637.       }
  1638.     }
  1639.     return $miscSegmentsFiltered;
  1640.   }
  1641.   /**
  1642.    * Translate WS errors to LMDVException errors
  1643.    *
  1644.    * @param string $errorType
  1645.    *    ErrorType returned by the APEX WS
  1646.    * @return int
  1647.    *    LMDVException constant
  1648.    */
  1649.   protected function getErrorType($errorType) {
  1650.     switch($errorType) {
  1651.       case self::ERROR_TYPE_SAPEIG:
  1652.         $error LMDVException::ERROR_SAPEIG;
  1653.         break;
  1654.       case self::ERROR_TYPE_NETWORK:
  1655.         $error LMDVException::ERROR_TIMEOUT;
  1656.         break;
  1657.       case self::ERROR_TYPE_SALESFORCE:
  1658.         $error LMDVException::ERROR_SALESFORCE;
  1659.         break;
  1660.       default:
  1661.         $error LMDVException::ERROR_UNKNOWN;
  1662.         break;
  1663.     }
  1664.     return $error;
  1665.   }
  1666.   /**
  1667.    * Translate WS errors to LMDVException errors
  1668.    *
  1669.    * @param string $errorType
  1670.    *    ErrorType returned by the APEX WS
  1671.    * @return int
  1672.    *    LMDVException constant
  1673.    */
  1674.   protected function getErrorTypeCreateBooking($errorType) {
  1675.     switch($errorType) {
  1676.       case self::ERROR_TYPE_SAPEIG:
  1677.         $error LMDVException::ERROR_SAPEIG_CREATEBOOKING;
  1678.         break;
  1679.       case self::ERROR_TYPE_NETWORK:
  1680.         $error LMDVException::ERROR_TIMEOUT_CREATEBOOKING;
  1681.         break;
  1682.       case self::ERROR_TYPE_SALESFORCE:
  1683.         $error LMDVException::ERROR_SALESFORCE_CREATEBOOKING;
  1684.         break;
  1685.       default:
  1686.         $error LMDVException::ERROR_UNKNOWN;
  1687.         break;
  1688.     }
  1689.     return $error;
  1690.   }
  1691.   /**
  1692.    * Clean ups SalesForce comments before showing them up in the form
  1693.    *
  1694.    * @param string $comment
  1695.    *    The comment that we receive from SalesForce
  1696.    * @return string
  1697.    *    The comment cleanup for showing in HTML
  1698.    */
  1699.   protected function cleanUpComments($comments) {
  1700.      $breaks = ['</p><p>''<br>''<br/>'];
  1701.      // Removes first opening <p> and last closing <p>
  1702.      $comments = \preg_replace('#<p>(.*)</p>#''$1', \htmlspecialchars_decode($commentsENT_QUOTES ENT_HTML401));
  1703.      // Translates breaks into "\n"
  1704.      foreach($breaks as $break) {
  1705.        $comments str_replace($break"\n"$comments);
  1706.      }
  1707.      // Strip any other HTML tags
  1708.      $comments strip_tags($comments);
  1709.      return $comments;
  1710.   }
  1711. }