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

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