vendor/uvdesk/mailbox-component/Services/MailboxService.php line 38

Open in your IDE?
  1. <?php
  2. namespace Webkul\UVDesk\MailboxBundle\Services;
  3. use Symfony\Component\Yaml\Yaml;
  4. use Doctrine\ORM\EntityManagerInterface;
  5. use PhpMimeMailParser\Parser as EmailParser;
  6. use Symfony\Component\HttpFoundation\RequestStack;
  7. use Symfony\Component\DependencyInjection\ContainerInterface;
  8. use Webkul\UVDesk\MailboxBundle\Utils\IMAP;
  9. use Webkul\UVDesk\MailboxBundle\Utils\SMTP;
  10. use Webkul\UVDesk\CoreFrameworkBundle\Entity\User;
  11. use Webkul\UVDesk\CoreFrameworkBundle\Entity\Ticket;
  12. use Webkul\UVDesk\CoreFrameworkBundle\Entity\Thread;
  13. use Webkul\UVDesk\CoreFrameworkBundle\Entity\Website;
  14. use Webkul\UVDesk\MailboxBundle\Utils\Mailbox\Mailbox;
  15. use Webkul\UVDesk\CoreFrameworkBundle\Utils\HTMLFilter;
  16. use Webkul\UVDesk\CoreFrameworkBundle\Entity\SupportRole;
  17. use Webkul\UVDesk\MailboxBundle\Utils\MailboxConfiguration;
  18. use Webkul\UVDesk\CoreFrameworkBundle\Workflow\Events as CoreWorkflowEvents;
  19. use Webkul\UVDesk\CoreFrameworkBundle\SwiftMailer\SwiftMailer as SwiftMailerService;
  20. class MailboxService
  21. {
  22.     const PATH_TO_CONFIG '/config/packages/uvdesk_mailbox.yaml';
  23.     private $parser;
  24.     private $container;
  25.     private $requestStack;
  26.     private $entityManager;
  27.     private $mailboxCollection = [];
  28.     public function __construct(ContainerInterface $containerRequestStack $requestStackEntityManagerInterface $entityManagerSwiftMailerService $swiftMailer)
  29.     {
  30.         $this->container $container;
  31.         $this->requestStack $requestStack;
  32.         $this->entityManager $entityManager;
  33.         $this->swiftMailer $swiftMailer;
  34.     }
  35.     public function getPathToConfigurationFile()
  36.     {
  37.         return $this->container->get('kernel')->getProjectDir() . self::PATH_TO_CONFIG;
  38.     }
  39.     public function createConfiguration($params)
  40.     {
  41.         $configuration = new MailboxConfigurations\MailboxConfiguration($params);
  42.         return $configuration ?? null;
  43.     }
  44.     public function parseMailboxConfigurations(bool $ignoreInvalidAttributes false
  45.     {
  46.         $path $this->getPathToConfigurationFile();
  47.         if (! file_exists($path)) {
  48.             throw new \Exception("File '$path' not found.");
  49.         }
  50.         // Read configurations from package config.
  51.         $mailboxConfiguration = new MailboxConfiguration();
  52.         foreach (Yaml::parse(file_get_contents($path))['uvdesk_mailbox']['mailboxes'] ?? [] as $id => $params) {
  53.             // Swiftmailer Configuration
  54.             
  55.             $swiftMailerConfigurations $this->swiftMailer->parseSwiftMailerConfigurations() ?? null;
  56.             if (isset($params['smtp_swift_mailer_server'])) {
  57.                 foreach ($swiftMailerConfigurations as $configuration) {
  58.                     if ($configuration->getId() == $params['smtp_swift_mailer_server']['mailer_id']) {
  59.                         $swiftMailerConfiguration $configuration;
  60.                         break;
  61.                     }
  62.                 }
  63.             }
  64.             // IMAP Configuration
  65.             $imapConfiguration null;
  66.             if (! empty($params['imap_server'])) {
  67.                 $imapConfiguration IMAP\Configuration::guessTransportDefinition($params['imap_server']);
  68.     
  69.                 if ($imapConfiguration instanceof IMAP\Transport\AppTransportConfigurationInterface) {
  70.                     $imapConfiguration
  71.                         ->setClient($params['imap_server']['client'])
  72.                         ->setUsername($params['imap_server']['username'])
  73.                     ;
  74.                 } else if ($imapConfiguration instanceof IMAP\Transport\SimpleTransportConfigurationInterface) {
  75.                     $imapConfiguration
  76.                         ->setUsername($params['imap_server']['username'])
  77.                     ;
  78.                 } else {
  79.                     $imapConfiguration
  80.                         ->setUsername($params['imap_server']['username'])
  81.                         ->setPassword($params['imap_server']['password'])
  82.                     ;
  83.                 }
  84.             }
  85.             // SMTP Configuration
  86.             $smtpConfiguration null
  87.             if (
  88.                 ! empty($params['smtp_server']) 
  89.                 && !isset($params['smtp_server']['mailer_id'])
  90.             ) {
  91.                 $smtpConfiguration SMTP\Configuration::guessTransportDefinition($params['smtp_server']);
  92.     
  93.                 if ($smtpConfiguration instanceof SMTP\Transport\AppTransportConfigurationInterface) {
  94.                     $smtpConfiguration
  95.                         ->setClient($params['smtp_server']['client'])
  96.                         ->setUsername($params['smtp_server']['username'])
  97.                     ;
  98.                 } else if ($smtpConfiguration instanceof SMTP\Transport\ResolvedTransportConfigurationInterface) {
  99.                     $smtpConfiguration
  100.                         ->setUsername($params['smtp_server']['username'])
  101.                         ->setPassword($params['smtp_server']['password'])
  102.                     ;
  103.                 }  else {
  104.                     $smtpConfiguration
  105.                         ->setHost($params['smtp_server']['host'])
  106.                         ->setPort($params['smtp_server']['port'])
  107.                         ->setUsername($params['smtp_server']['username'])
  108.                         ->setPassword($params['smtp_server']['password'])
  109.                     ;
  110.                     if (! empty($params['smtp_server']['sender_address'])) {
  111.                         $smtpConfiguration
  112.                             ->setSenderAddress($params['smtp_server']['sender_address'])
  113.                         ;
  114.                     }
  115.                 }
  116.             }
  117.             // Mailbox Configuration
  118.             ($mailbox = new Mailbox($id))
  119.                 ->setName($params['name'])
  120.                 ->setIsEnabled($params['enabled']);
  121.             if (! empty($imapConfiguration)) {
  122.                 $mailbox
  123.                     ->setImapConfiguration($imapConfiguration)
  124.                 ;
  125.             }
  126.             if (! empty($smtpConfiguration)) {
  127.                 $mailbox
  128.                     ->setSmtpConfiguration($smtpConfiguration)
  129.                 ;
  130.             }
  131.             
  132.             if (! empty($swiftMailerConfiguration)) {
  133.                 $mailbox->setSwiftMailerConfiguration($swiftMailerConfiguration);
  134.             } else if (! empty($params['smtp_server']['mailer_id']) && true === $ignoreInvalidAttributes) {
  135.                 $mailbox->setSwiftMailerConfiguration($swiftmailerService->createConfiguration('smtp'$params['smtp_server']['mailer_id']));
  136.             }
  137.             $mailboxConfiguration->addMailbox($mailbox);
  138.         }
  139.         return $mailboxConfiguration;
  140.     }
  141.     private function getParser()
  142.     {
  143.         if (empty($this->parser)) {
  144.             $this->parser = new EmailParser();
  145.         }
  146.         return $this->parser;
  147.     }
  148.     private function getLoadedEmailContentParser($emailContents null$cacheContent true): ?EmailParser
  149.     {
  150.         if (empty($emailContents)) {
  151.             return $this->emailParser ?? null;
  152.         }
  153.         $emailParser = new EmailParser();
  154.         $emailParser
  155.             ->setText($emailContents)
  156.         ;
  157.         if ($cacheContent) {
  158.             $this->emailParser $emailParser;
  159.         }
  160.         return $emailParser;
  161.     }
  162.     private function getRegisteredMailboxes()
  163.     {
  164.         if (empty($this->mailboxCollection)) {
  165.             $this->mailboxCollection array_map(function ($mailboxId) {
  166.                 return $this->container->getParameter("uvdesk.mailboxes.$mailboxId");
  167.             }, $this->container->getParameter('uvdesk.mailboxes'));
  168.         }
  169.         return $this->mailboxCollection;
  170.     }
  171.     public function getRegisteredMailboxesById()
  172.     {
  173.         // Fetch existing content in file
  174.         $filePath $this->getPathToConfigurationFile();
  175.         $file_content file_get_contents($filePath);
  176.         // Convert yaml file content into array and merge existing mailbox and new mailbox
  177.         $file_content_array Yaml::parse($file_content6);
  178.         if ($file_content_array['uvdesk_mailbox']['mailboxes']) {
  179.             foreach ($file_content_array['uvdesk_mailbox']['mailboxes'] as $key => $value) {
  180.                 $value['mailbox_id'] = $key;
  181.                 $mailboxCollection[] = $value;
  182.             }
  183.         }
  184.         
  185.         return $mailboxCollection ?? [];
  186.     }
  187.     public function getEmailAddresses($collection)
  188.     {
  189.         $formattedCollection array_map(function ($emailAddress) {
  190.             if (filter_var($emailAddress['address'], FILTER_VALIDATE_EMAIL)) {
  191.                 return $emailAddress['address'];
  192.             }
  193.             return null;
  194.         }, (array) $collection);
  195.         $filteredCollection array_values(array_filter($formattedCollection));
  196.         return count($filteredCollection) == $filteredCollection[0] : $filteredCollection;
  197.     }
  198.     public function parseAddress($type)
  199.     {
  200.         $addresses mailparse_rfc822_parse_addresses($this->getParser()->getHeader($type));
  201.         return $addresses ?: false;
  202.     }
  203.     public function getEmailAddress($addresses)
  204.     {
  205.         foreach ((array) $addresses as $address) {
  206.             if (filter_var($address['address'], FILTER_VALIDATE_EMAIL)) {
  207.                 return $address['address'];
  208.             }
  209.         }
  210.         return null;
  211.     }
  212.     public function getMailboxByEmail($email)
  213.     {
  214.         foreach ($this->getRegisteredMailboxes() as $registeredMailbox) {
  215.             if (strtolower($email) === strtolower($registeredMailbox['imap_server']['username'])) {
  216.                 return $registeredMailbox;
  217.             }
  218.         }
  219.         throw new \Exception("No mailbox found for email '$email'");
  220.     }
  221.     
  222.     public function getMailboxByToEmail($email)
  223.     {
  224.         foreach ($this->getRegisteredMailboxes() as $registeredMailbox) {
  225.             if (strtolower($email) === strtolower($registeredMailbox['imap_server']['username'])) {
  226.                 return true;
  227.             }
  228.         }
  229.         return false;
  230.     }
  231.     private function searchTicketSubjectReference($senderEmail$messageSubject) {
  232.         
  233.         // Search Criteria: Find ticket based on subject
  234.         if (
  235.             ! empty($senderEmail)
  236.             && ! empty($messageSubject)
  237.         ) {
  238.             $threadRepository $this->entityManager->getRepository(Thread::class);
  239.             $ticket $threadRepository->findTicketBySubject($senderEmail$messageSubject);
  240.             if ($ticket  != null) {
  241.                 return $ticket;
  242.             }
  243.         }
  244.         return null;
  245.     }
  246.     private function searchExistingTickets(array $criterias = [])
  247.     {
  248.         if (empty($criterias)) {
  249.             return null;
  250.         }
  251.         $ticketRepository $this->entityManager->getRepository(Ticket::class);
  252.         $threadRepository $this->entityManager->getRepository(Thread::class);
  253.         foreach ($criterias as $criteria => $criteriaValue) {
  254.             if (empty($criteriaValue)) {
  255.                 continue;
  256.             }
  257.             switch ($criteria) {
  258.                 case 'messageId':
  259.                     // Search Criteria 1: Find ticket by unique message id
  260.                     $ticket $ticketRepository->findOneByReferenceIds($criteriaValue);
  261.                     if (! empty($ticket)) {
  262.                         return $ticket;
  263.                     } else {
  264.                         $thread $threadRepository->findOneByMessageId($criteriaValue);
  265.         
  266.                         if (! empty($thread)) {
  267.                             return $thread->getTicket();
  268.                         }
  269.                     }
  270.                     
  271.                     break;
  272.                 case 'outlookConversationId':
  273.                     // Search Criteria 1: Find ticket by unique message id
  274.                     $ticket $ticketRepository->findOneByOutlookConversationId($criteriaValue);
  275.                     if (! empty($ticket)) {
  276.                         return $ticket;
  277.                     }
  278.                     
  279.                     break;
  280.                 case 'inReplyTo':
  281.                     // Search Criteria 2: Find ticket based on in-reply-to reference id
  282.                     $ticket $this->entityManager->getRepository(Thread::class)->findThreadByRefrenceId($criteriaValue);
  283.                     if (! empty($ticket)) {
  284.                         return $ticket;
  285.                     } else {
  286.                         $thread $threadRepository->findOneByMessageId($criteriaValue);
  287.         
  288.                         if (! empty($thread)) {
  289.                             return $thread->getTicket();
  290.                         }
  291.                     }
  292.                     break;
  293.                 case 'referenceIds':
  294.                     // Search Criteria 3: Find ticket based on reference id
  295.                     // Break references into ind. message id collection, and iteratively 
  296.                     // search for existing threads for these message ids.
  297.                     $referenceIds explode(' '$criteriaValue);
  298.                     foreach ($referenceIds as $messageId) {
  299.                         $thread $threadRepository->findOneByMessageId($messageId);
  300.                         if (! empty($thread)) {
  301.                             return $thread->getTicket();
  302.                         }
  303.                     }
  304.                     break;
  305.                 default:
  306.                     break;
  307.             }
  308.         }
  309.         return null;
  310.     }
  311.     
  312.     public function processMail($rawEmail)
  313.     {
  314.         $mailData = [];
  315.         $parser $this->getParser();
  316.         $parser->setText($rawEmail);
  317.         $from $this->parseAddress('from') ?: $this->parseAddress('sender');
  318.         $addresses = [
  319.             'from'         => $this->getEmailAddress($from),
  320.             'to'           => empty($this->parseAddress('X-Forwarded-To')) ? $this->parseAddress('to') : $this->parseAddress('X-Forwarded-To'),
  321.             'cc'           => $this->parseAddress('cc'),
  322.             'delivered-to' => $this->parseAddress('delivered-to'),
  323.         ];
  324.         if (empty($addresses['from'])) {
  325.             return [
  326.                 'message' => "No 'from' email address was found while processing contents of email."
  327.                 'content' => [], 
  328.             ];
  329.         } else {
  330.             if (! empty($addresses['delivered-to'])) {
  331.                 $addresses['to'] = array_map(function($address) {
  332.                     return $address['address'];
  333.                 }, $addresses['delivered-to']);
  334.             } else if (! empty($addresses['to'])) {
  335.                 $addresses['to'] = array_map(function($address) {
  336.                     return $address['address'];
  337.                 }, $addresses['to']);
  338.             } else if (! empty($addresses['cc'])) {
  339.                 $addresses['to'] = array_map(function($address) {
  340.                     return $address['address'];
  341.                 }, $addresses['cc']);
  342.             }
  343.             
  344.             // Skip email processing if no to-emails are specified
  345.             if (empty($addresses['to'])) {
  346.                 return [
  347.                     'message' => "No 'to' email addresses were found in the email."
  348.                     'content' => [
  349.                         'from' => ! empty($addresses['from']) ? $addresses['from'] : null
  350.                     ], 
  351.                 ];
  352.             }
  353.             // Skip email processing if email is an auto-forwarded message to prevent infinite loop.
  354.             if ($parser->getHeader('precedence') || $parser->getHeader('x-autoreply') || $parser->getHeader('x-autorespond') || 'auto-replied' == $parser->getHeader('auto-submitted')) {
  355.                 return [
  356.                     'message' => "Received an auto-forwarded email which can lead to possible infinite loop of email exchanges. Skipping email from further processing."
  357.                     'content' => [
  358.                         'from' => ! empty($addresses['from']) ? $addresses['from'] : null
  359.                     ], 
  360.                 ];
  361.             }
  362.             // Check for self-referencing. Skip email processing if a mailbox is configured by the sender's address.
  363.             try {
  364.                 $this->getMailboxByEmail($addresses['from']);
  365.                 return [
  366.                     'message' => "Received a self-referencing email where the sender email address matches one of the configured mailbox address. Skipping email from further processing."
  367.                     'content' => [
  368.                         'from' => !empty($addresses['from']) ? $addresses['from'] : null
  369.                     ], 
  370.                 ];
  371.             } catch (\Exception $e) {
  372.                 // An exception being thrown means no mailboxes were found from the recipient's address. Continue processing.
  373.             }
  374.         }
  375.         $mailData['replyTo'] = '';
  376.         
  377.         foreach ($addresses['to'] as $mailboxEmail){
  378.             if ($this->getMailboxByToEmail(strtolower($mailboxEmail))) {
  379.                 $mailData['replyTo'] = $mailboxEmail;
  380.             }
  381.         }
  382.         // Process Mail - References
  383.         $addresses['to'][0] = isset($mailData['replyTo']) ? strtolower($mailData['replyTo']) : strtolower($addresses['to'][0]);
  384.         $mailData['replyTo'] = $addresses['to'];
  385.         $mailData['messageId'] = $parser->getHeader('message-id') ?: null;
  386.         $mailData['inReplyTo'] = htmlspecialchars_decode($parser->getHeader('in-reply-to'));
  387.         $mailData['referenceIds'] = htmlspecialchars_decode($parser->getHeader('references'));
  388.         $mailData['cc'] = array_filter(explode(','$parser->getHeader('cc'))) ?: [];
  389.         $mailData['bcc'] = array_filter(explode(','$parser->getHeader('bcc'))) ?: [];
  390.         // Process Mail - User Details
  391.         $mailData['source'] = 'email';
  392.         $mailData['createdBy'] = 'customer';
  393.         $mailData['role'] = 'ROLE_CUSTOMER';
  394.         $mailData['from'] = $addresses['from'];
  395.         $mailData['name'] = trim(current(explode('@'$from[0]['display'])));
  396.         // Process Mail - Content
  397.         try {
  398.             $htmlFilter = new HTMLFilter();
  399.             $mailData['subject'] = $parser->getHeader('subject');
  400.             $mailData['message'] = autolink($htmlFilter->addClassEmailReplyQuote($parser->getMessageBody('htmlEmbedded')));
  401.             $mailData['attachments'] = $parser->getAttachments();
  402.         } catch(\Exception $e) {
  403.             return [
  404.                 'error'   => true,
  405.                 'message' => $e->getMessage(),
  406.             ];
  407.         }
  408.         
  409.         if (! $mailData['message']) {
  410.             $mailData['message'] = autolink($htmlFilter->addClassEmailReplyQuote($parser->getMessageBody('text')));
  411.         }
  412.         $website $this->entityManager->getRepository(Website::class)->findOneByCode('knowledgebase');
  413.         
  414.         if (! empty($mailData['from']) && $this->container->get('ticket.service')->isEmailBlocked($mailData['from'], $website)) {
  415.             return [
  416.                 'message' => "Received email where the sender email address is present in the block list. Skipping this email from further processing."
  417.                 'content' => [
  418.                     'from' => !empty($mailData['from']) ? $mailData['from'] : null
  419.                 ], 
  420.             ];
  421.         }
  422.         // Search for any existing tickets
  423.         $ticket $this->searchExistingTickets([
  424.             'messageId'    => $mailData['messageId'],
  425.             'inReplyTo'    => $mailData['inReplyTo'],
  426.             'referenceIds' => $mailData['referenceIds'],
  427.             'from'         => $mailData['from'],
  428.             'subject'      => $mailData['subject'],
  429.         ]);
  430.         if (empty($ticket)) {
  431.             $mailData['threadType'] = 'create';
  432.             $mailData['referenceIds'] = $mailData['messageId'];
  433.             // @Todo For same subject with same customer check
  434.             // $ticketSubjectReferenceExist = $this->searchTicketSubjectReference($mailData['from'], $mailData['subject']);
  435.             // if (!empty($ticketSubjectReferenceExist)) {
  436.             //     return;
  437.             // }
  438.             $thread $this->container->get('ticket.service')->createTicket($mailData);
  439.             // Trigger ticket created event
  440.             $event = new CoreWorkflowEvents\Ticket\Create();
  441.             $event
  442.                 ->setTicket($thread->getTicket())
  443.             ;
  444.             $this->container->get('event_dispatcher')->dispatch($event'uvdesk.automation.workflow.execute');
  445.         } else if (false === $ticket->getIsTrashed() && strtolower($ticket->getStatus()->getCode()) != 'spam' && !empty($mailData['inReplyTo'])) {
  446.             $mailData['threadType'] = 'reply';
  447.             $thread $this->entityManager->getRepository(Thread::class)->findOneByMessageId($mailData['messageId']);
  448.             $ticketRef $this->entityManager->getRepository(Ticket::class)->findById($ticket->getId());
  449.             $referenceIds explode(' '$ticketRef[0]->getReferenceIds());
  450.             if (!empty($thread)) {
  451.                 // Thread with the same message id exists skip process.
  452.                 return [
  453.                     'message' => "The contents of this email has already been processed."
  454.                     'content' => [
  455.                         'from'   => ! empty($mailData['from']) ? $mailData['from'] : null,
  456.                         'thread' => $thread->getId(),
  457.                         'ticket' => $ticket->getId(),
  458.                     ], 
  459.                 ];
  460.             }
  461.             if (in_array($mailData['messageId'], $referenceIds)) {
  462.                 // Thread with the same message id exists skip process.
  463.                 return [
  464.                     'message' => "The contents of this email has already been processed."
  465.                     'content' => [
  466.                         'from' => !empty($mailData['from']) ? $mailData['from'] : null
  467.                     ], 
  468.                 ];
  469.             }
  470.             if (
  471.                 $ticket->getCustomer() 
  472.                 && $ticket->getCustomer()->getEmail() == $mailData['from']
  473.             ) {
  474.                 // Reply from customer
  475.                 $user $ticket->getCustomer();
  476.                 $mailData['user'] = $user;
  477.                 $userDetails $user->getCustomerInstance()->getPartialDetails();
  478.             } else if ($this->entityManager->getRepository(Ticket::class)->isTicketCollaborator($ticket$mailData['from'])) {
  479.                 // Reply from collaborator
  480.                 $user $this->entityManager->getRepository(User::class)->findOneByEmail($mailData['from']);
  481.                 $mailData['user'] = $user;
  482.                 $mailData['createdBy'] = 'collaborator';
  483.                 $userDetails $user->getCustomerInstance()->getPartialDetails();
  484.             } else {
  485.                 $user $this->entityManager->getRepository(User::class)->findOneByEmail($mailData['from']);
  486.                 
  487.                 if (
  488.                     ! empty($user
  489.                     && null != $user->getAgentInstance()
  490.                 ) {
  491.                     $mailData['user'] = $user;
  492.                     $mailData['createdBy'] = 'agent';
  493.                     $userDetails $user->getAgentInstance()->getPartialDetails();
  494.                 } else {
  495.                     // Add user as a ticket collaborator
  496.                     if (empty($user)) {
  497.                         // Create a new user instance with customer support role
  498.                         $role $this->entityManager->getRepository(SupportRole::class)->findOneByCode('ROLE_CUSTOMER');
  499.                         $user $this->container->get('user.service')->createUserInstance($mailData['from'], $mailData['name'], $role, [
  500.                             'source' => 'email',
  501.                             'active' => true
  502.                         ]);
  503.                     }
  504.                     $mailData['user'] = $user;
  505.                     $userDetails $user->getCustomerInstance()->getPartialDetails();
  506.                     if (false == $this->entityManager->getRepository(Ticket::class)->isTicketCollaborator($ticket$mailData['from'])) {
  507.                         $ticket->addCollaborator($user);
  508.                         $this->entityManager->persist($ticket);
  509.                         $this->entityManager->flush();
  510.                         $ticket->lastCollaborator $user;
  511.                                
  512.                         $event = new CoreWorkflowEvents\Ticket\Collaborator();
  513.                         $event
  514.                             ->setTicket($ticket)
  515.                         ;
  516.                         $this->container->get('event_dispatcher')->dispatch($event'uvdesk.automation.workflow.execute');
  517.                     }
  518.                 }
  519.             }
  520.             $mailData['fullname'] = $userDetails['name'];
  521.             
  522.             $thread $this->container->get('ticket.service')->createThread($ticket$mailData);
  523.             
  524.             if ($thread->getThreadType() == 'reply') {
  525.                 if ($thread->getCreatedBy() == 'customer') {
  526.                     $event = new CoreWorkflowEvents\Ticket\CustomerReply();
  527.                     $event
  528.                         ->setTicket($ticket)
  529.                     ;
  530.                 }  else if ($thread->getCreatedBy() == 'collaborator') {
  531.                     $event = new CoreWorkflowEvents\Ticket\CollaboratorReply();
  532.                     $event
  533.                         ->setTicket($ticket)
  534.                     ;
  535.                 } else {
  536.                     $event = new CoreWorkflowEvents\Ticket\AgentReply();
  537.                     $event
  538.                         ->setTicket($ticket)
  539.                     ;
  540.                 }
  541.             }
  542.             // Trigger thread reply event
  543.             $this->container->get('event_dispatcher')->dispatch($event'uvdesk.automation.workflow.execute');
  544.         } else if (false === $ticket->getIsTrashed() && strtolower($ticket->getStatus()->getCode()) != 'spam' && empty($mailData['inReplyTo'])) {
  545.             return [
  546.                 'message' => "The contents of this email has already been processed."
  547.                 'content' => [
  548.                     'from'   => ! empty($mailData['from']) ? $mailData['from'] : null
  549.                     'thread' => ! empty($thread) ? $thread->getId() : null
  550.                     'ticket' => ! empty($ticket) ? $ticket->getId() : null
  551.                 ], 
  552.             ];
  553.         }
  554.         return [
  555.             'message' => "Inbound email processed successfully."
  556.             'content' => [
  557.                 'from'   => ! empty($mailData['from']) ? $mailData['from'] : null
  558.                 'thread' => ! empty($thread) ? $thread->getId() : null
  559.                 'ticket' => ! empty($ticket) ? $ticket->getId() : null
  560.             ], 
  561.         ];
  562.     }
  563.     public function processOutlookMail(array $outlookEmail)
  564.     {
  565.         $mailData = [];
  566.         $senderName null;
  567.         $senderAddress null;
  568.         if (! empty($outlookEmail['from']['emailAddress']['address'])) {
  569.             $senderName $outlookEmail['from']['emailAddress']['name'];
  570.             $senderAddress $outlookEmail['from']['emailAddress']['address'];
  571.         } else if (! empty($outlookEmail['sender']['emailAddress']['address'])) {
  572.             $senderName $outlookEmail['sender']['emailAddress']['name'];
  573.             $senderAddress $outlookEmail['sender']['emailAddress']['address'];
  574.         } else {
  575.             return [
  576.                 'message' => "No 'from' email address was found while processing contents of email."
  577.                 'content' => [], 
  578.             ];
  579.         }
  580.         $toRecipients array_map(function ($recipient) { return $recipient['emailAddress']['address']; }, $outlookEmail['toRecipients']);
  581.         $ccRecipients array_map(function ($recipient) { return $recipient['emailAddress']['address']; }, $outlookEmail['ccRecipients'] ?? []);
  582.         $bccRecipients array_map(function ($recipient) { return $recipient['emailAddress']['address']; }, $outlookEmail['bccRecipients'] ?? []);
  583.         $addresses = [
  584.             'from' => $senderAddress
  585.             'to'   => $toRecipients
  586.             'cc'   => $ccRecipients
  587.         ];
  588.         
  589.         // Skip email processing if no to-emails are specified
  590.         if (empty($addresses['to'])) {
  591.             return [
  592.                 'message' => "No 'to' email addresses were found in the email."
  593.                 'content' => [
  594.                     'from' => $senderAddress ?? null
  595.                 ], 
  596.             ];
  597.         }
  598.         // Check for self-referencing. Skip email processing if a mailbox is configured by the sender's address.
  599.         try {
  600.             $this->getMailboxByEmail($senderAddress);
  601.             return [
  602.                 'message' => "Received a self-referencing email where the sender email address matches one of the configured mailbox address. Skipping email from further processing."
  603.                 'content' => [
  604.                     'from' => $senderAddress ?? null
  605.                 ], 
  606.             ];
  607.         } catch (\Exception $e) {
  608.             // An exception being thrown means no mailboxes were found from the recipient's address. Continue processing.
  609.         }
  610.         // Process Mail - References
  611.         // $addresses['to'][0] = isset($mailData['replyTo']) ? strtolower($mailData['replyTo']) : strtolower($addresses['to'][0]);
  612.         $mailData['replyTo'] = $addresses['to'];
  613.         $mailData['messageId'] = $outlookEmail['internetMessageId'];
  614.         $mailData['outlookConversationId'] = $outlookEmail['conversationId'];
  615.         $mailData['inReplyTo'] = $outlookEmail['conversationId'];
  616.         // $mailData['inReplyTo'] = htmlspecialchars_decode($parser->getHeader('in-reply-to'));
  617.         $mailData['referenceIds'] = '';
  618.         // $mailData['referenceIds'] = htmlspecialchars_decode($parser->getHeader('references'));
  619.         $mailData['cc'] = $ccRecipients;
  620.         $mailData['bcc'] = $bccRecipients;
  621.         // Process Mail - User Details
  622.         $mailData['source'] = 'email';
  623.         $mailData['createdBy'] = 'customer';
  624.         $mailData['role'] = 'ROLE_CUSTOMER';
  625.         $mailData['from'] = $senderAddress;
  626.         $mailData['name'] = trim($senderName);
  627.         // Process Mail - Content
  628.         $htmlFilter = new HTMLFilter();
  629.         $mailData['subject'] = $outlookEmail['subject'];
  630.         $mailData['message'] = autolink($htmlFilter->addClassEmailReplyQuote($outlookEmail['body']['content']));
  631.         $mailData['attachments'] = [];
  632.         $mailData['attachmentContent'] = isset($outlookEmail['outlookAttachments']) ? $outlookEmail['outlookAttachments'] : [];
  633.         $website $this->entityManager->getRepository(Website::class)->findOneByCode('knowledgebase');
  634.         
  635.         if (
  636.             ! empty($mailData['from'])
  637.             && $this->container->get('ticket.service')->isEmailBlocked($mailData['from'], $website)
  638.         ) {
  639.             return [
  640.                 'message' => "Received email where the sender email address is present in the block list. Skipping this email from further processing."
  641.                 'content' => [
  642.                     'from' => !empty($mailData['from']) ? $mailData['from'] : null
  643.                 ], 
  644.             ];
  645.         }
  646.         // return [
  647.         //     'outlookConversationId' => $mailData['outlookConversationId'],
  648.         //     'message' => "No 'to' email addresses were found in the email.", 
  649.         //     'content' => [
  650.         //         'outlookConversationId' => $mailData['outlookConversationId'],
  651.         //     ], 
  652.         // ];
  653.         // Search for any existing tickets
  654.         $ticket $this->searchExistingTickets([
  655.             'messageId'             => $mailData['messageId'],
  656.             'inReplyTo'             => $mailData['inReplyTo'],
  657.             'referenceIds'          => $mailData['referenceIds'],
  658.             'from'                  => $mailData['from'],
  659.             'subject'               => $mailData['subject'], 
  660.             'outlookConversationId' => $mailData['outlookConversationId'],
  661.         ]);
  662.         if (empty($ticket)) {
  663.             $mailData['threadType'] = 'create';
  664.             $mailData['referenceIds'] = $mailData['messageId'];
  665.             // @Todo For same subject with same customer check
  666.             // $ticketSubjectReferenceExist = $this->searchTicketSubjectReference($mailData['from'], $mailData['subject']);
  667.             // if(!empty($ticketSubjectReferenceExist)) {
  668.             //     return;
  669.             // }
  670.             $thread $this->container->get('ticket.service')->createTicket($mailData);
  671.             // Trigger ticket created event
  672.             $event = new CoreWorkflowEvents\Ticket\Create();
  673.             $event
  674.                 ->setTicket($thread->getTicket())
  675.             ;
  676.             $this->container->get('event_dispatcher')->dispatch($event'uvdesk.automation.workflow.execute');
  677.         } else if (
  678.             false === $ticket->getIsTrashed()
  679.             && strtolower($ticket->getStatus()->getCode()) != 'spam'
  680.             && ! empty($mailData['inReplyTo'])
  681.         ) {
  682.             $mailData['threadType'] = 'reply';
  683.             $thread $this->entityManager->getRepository(Thread::class)->findOneByMessageId($mailData['messageId']);
  684.             $ticketRef $this->entityManager->getRepository(Ticket::class)->findById($ticket->getId());
  685.             $referenceIds explode(' '$ticketRef[0]->getReferenceIds());
  686.             if (! empty($thread)) {
  687.                 // Thread with the same message id exists skip process.
  688.                 return [
  689.                     'message' => "The contents of this email has already been processed 1."
  690.                     'content' => [
  691.                         'from'   => ! empty($mailData['from']) ? $mailData['from'] : null
  692.                         'thread' => $thread->getId(), 
  693.                         'ticket' => $ticket->getId(), 
  694.                     ], 
  695.                 ];
  696.             }
  697.             if (in_array($mailData['messageId'], $referenceIds)) {
  698.                 // Thread with the same message id exists skip process.
  699.                 return [
  700.                     'message' => "The contents of this email has already been processed 2."
  701.                     'content' => [
  702.                         'from' => !empty($mailData['from']) ? $mailData['from'] : null
  703.                     ], 
  704.                 ];
  705.             }
  706.             if ($ticket->getCustomer() && $ticket->getCustomer()->getEmail() == $mailData['from']) {
  707.                 // Reply from customer
  708.                 $user $ticket->getCustomer();
  709.                 $mailData['user'] = $user;
  710.                 $userDetails $user->getCustomerInstance()->getPartialDetails();
  711.             } else if ($this->entityManager->getRepository(Ticket::class)->isTicketCollaborator($ticket$mailData['from'])){
  712.                 // Reply from collaborator
  713.                 $user $this->entityManager->getRepository(User::class)->findOneByEmail($mailData['from']);
  714.                 $mailData['user'] = $user;
  715.                 $mailData['createdBy'] = 'collaborator';
  716.                 $userDetails $user->getCustomerInstance()->getPartialDetails();
  717.             } else {
  718.                 $user $this->entityManager->getRepository(User::class)->findOneByEmail($mailData['from']);
  719.                 if (! empty($user) && null != $user->getAgentInstance()) {
  720.                     $mailData['user'] = $user;
  721.                     $mailData['createdBy'] = 'agent';
  722.                     $userDetails $user->getAgentInstance()->getPartialDetails();
  723.                 } else {
  724.                     // Add user as a ticket collaborator
  725.                     if (empty($user)) {
  726.                         // Create a new user instance with customer support role
  727.                         $role $this->entityManager->getRepository(SupportRole::class)->findOneByCode('ROLE_CUSTOMER');
  728.                         $user $this->container->get('user.service')->createUserInstance($mailData['from'], $mailData['name'], $role, [
  729.                             'source' => 'email',
  730.                             'active' => true
  731.                         ]);
  732.                     }
  733.                     $mailData['user'] = $user;
  734.                     $userDetails $user->getCustomerInstance()->getPartialDetails();
  735.                     if (false == $this->entityManager->getRepository(Ticket::class)->isTicketCollaborator($ticket$mailData['from'])) {
  736.                         $ticket->addCollaborator($user);
  737.                         $this->entityManager->persist($ticket);
  738.                         $this->entityManager->flush();
  739.                         $ticket->lastCollaborator $user;
  740.                         
  741.                         $event = new CoreWorkflowEvents\Ticket\Collaborator();
  742.                         $event
  743.                             ->setTicket($ticket)
  744.                         ;
  745.                         $this->container->get('event_dispatcher')->dispatch($event'uvdesk.automation.workflow.execute');
  746.                     }
  747.                 }
  748.             }
  749.             $mailData['fullname'] = $userDetails['name'];
  750.             
  751.             $thread $this->container->get('ticket.service')->createThread($ticket$mailData);
  752.             
  753.             if ($thread->getThreadType() == 'reply') {
  754.                 if ($thread->getCreatedBy() == 'customer') {
  755.                     $event = new CoreWorkflowEvents\Ticket\CustomerReply();
  756.                     $event
  757.                         ->setTicket($ticket)
  758.                     ;
  759.                 }  else if ($thread->getCreatedBy() == 'collaborator') {
  760.                     $event = new CoreWorkflowEvents\Ticket\CollaboratorReply();
  761.                     $event
  762.                         ->setTicket($ticket)
  763.                     ;
  764.                 } else {
  765.                     $event = new CoreWorkflowEvents\Ticket\AgentReply();
  766.                     $event
  767.                         ->setTicket($ticket)
  768.                     ;
  769.                 }
  770.             }
  771.             // Trigger thread reply event
  772.             $this->container->get('event_dispatcher')->dispatch($event'uvdesk.automation.workflow.execute');
  773.         } else if (false === $ticket->getIsTrashed() && strtolower($ticket->getStatus()->getCode()) != 'spam' && empty($mailData['inReplyTo'])) {
  774.             return [
  775.                 'message' => "The contents of this email has already been processed 3."
  776.                 'content' => [
  777.                     'from'   => ! empty($mailData['from']) ? $mailData['from'] : null
  778.                     'thread' => ! empty($thread) ? $thread->getId() : null
  779.                     'ticket' => ! empty($ticket) ? $ticket->getId() : null
  780.                 ], 
  781.             ];
  782.         }
  783.         return [
  784.             'message' => "Inbound email processed successfully."
  785.             'content' => [
  786.                 'from'   => ! empty($mailData['from']) ? $mailData['from'] : null
  787.                 'thread' => ! empty($thread) ? $thread->getId() : null
  788.                 'ticket' => ! empty($ticket) ? $ticket->getId() : null
  789.             ],
  790.         ];
  791.     }
  792. }