vendor/uvdesk/core-framework/Services/TicketService.php line 50

Open in your IDE?
  1. <?php
  2. namespace Webkul\UVDesk\CoreFrameworkBundle\Services;
  3. use Doctrine\ORM\Query;
  4. use Doctrine\ORM\EntityManagerInterface;
  5. use Symfony\Component\Filesystem\Filesystem;
  6. use Symfony\Component\HttpFoundation\Request;
  7. use Symfony\Component\HttpFoundation\RequestStack;
  8. use Symfony\Contracts\Translation\TranslatorInterface;
  9. use Symfony\Component\DependencyInjection\ContainerInterface;
  10. use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
  11. use Webkul\UVDesk\SupportCenterBundle\Entity\Article;
  12. use Webkul\UVDesk\CoreFrameworkBundle\Entity\Website;
  13. use Webkul\UVDesk\MailboxBundle\Services\MailboxService;
  14. use Webkul\UVDesk\CoreFrameworkBundle\Utils\TokenGenerator;
  15. use Webkul\UVDesk\CoreFrameworkBundle\Services\UserService;
  16. use Webkul\UVDesk\AutomationBundle\Entity\PreparedResponses;
  17. use UVDesk\CommunityPackages\UVDesk as UVDeskCommunityPackages;
  18. use Webkul\UVDesk\CoreFrameworkBundle\Services\FileUploadService;
  19. use Webkul\UVDesk\SupportCenterBundle\Entity\KnowledgebaseWebsite;
  20. use Webkul\UVDesk\CoreFrameworkBundle\Entity as CoreFrameworkEntity;
  21. use Webkul\UVDesk\CoreFrameworkBundle\Workflow\Events as CoreWorkflowEvents;
  22. class TicketService
  23. {
  24.     const PATH_TO_CONFIG '/config/packages/uvdesk_mailbox.yaml';
  25.     protected $container;
  26.     protected $requestStack;
  27.     protected $entityManager;
  28.     protected $fileUploadService;
  29.     protected $userService;
  30.     public function __construct(
  31.         ContainerInterface $container,
  32.         RequestStack $requestStack,
  33.         EntityManagerInterface $entityManager,
  34.         FileUploadService $fileUploadService,
  35.         UserService $userService,
  36.         MailboxService $mailboxService,
  37.         TranslatorInterface $translator
  38.     ) {
  39.         $this->container $container;
  40.         $this->requestStack $requestStack;
  41.         $this->entityManager $entityManager;
  42.         $this->fileUploadService $fileUploadService;
  43.         $this->userService $userService;
  44.         $this->mailboxService $mailboxService;
  45.         $this->translator $translator;
  46.     }
  47.     public function getAllMailboxes()
  48.     {
  49.         $mailboxConfiguration $this->mailboxService->parseMailboxConfigurations();
  50.         $collection array_map(function ($mailbox) {
  51.             return [
  52.                 'id'        => $mailbox->getId(),
  53.                 'name'      => $mailbox->getName(),
  54.                 'isEnabled' => $mailbox->getIsEnabled(),
  55.                 'email'     => $mailbox->getImapConfiguration()->getUsername(),
  56.             ];
  57.         }, array_values($mailboxConfiguration->getMailboxes()));
  58.         return ($collection ?? []);
  59.     }
  60.     public function generateRandomEmailReferenceId()
  61.     {
  62.         $emailDomain null;
  63.         $mailbox $this->mailboxService->parseMailboxConfigurations()->getDefaultMailbox();
  64.         if (!empty($mailbox)) {
  65.             $smtpConfiguration $mailbox->getSmtpConfiguration();
  66.             if (!empty($smtpConfiguration)) {
  67.                 $emailDomain substr($smtpConfiguration->getUsername(), strpos($smtpConfiguration->getUsername(), '@'));
  68.             }
  69.         }
  70.         if (!empty($emailDomain)) {
  71.             return sprintf("<%s%s>"TokenGenerator::generateToken(20'0123456789abcdefghijklmnopqrstuvwxyz'), $emailDomain);
  72.         }
  73.         return null;
  74.     }
  75.     // @TODO: Refactor this out of this service. Use UserService::getSessionUser() instead.
  76.     public function getUser()
  77.     {
  78.         return $this->container->get('user.service')->getCurrentUser();
  79.     }
  80.     public function getDefaultType()
  81.     {
  82.         $typeCode $this->container->getParameter('uvdesk.default.ticket.type');
  83.         $ticketType $this->entityManager->getRepository(CoreFrameworkEntity\TicketType::class)->findOneByCode($typeCode);
  84.         return !empty($ticketType) ? $ticketType null;
  85.     }
  86.     public function getDefaultStatus()
  87.     {
  88.         $statusCode $this->container->getParameter('uvdesk.default.ticket.status');
  89.         $ticketStatus $this->entityManager->getRepository(CoreFrameworkEntity\TicketStatus::class)->findOneByCode($statusCode);
  90.         return !empty($ticketStatus) ? $ticketStatus null;
  91.     }
  92.     public function getUserPresenceStatus()
  93.     {
  94.         $presenceStatus $this->entityManager->getRepository(CoreFrameworkEntity\Website::class)->findOneById(1);
  95.         return !empty($presenceStatus) ? $presenceStatus->getDisplayUserPresenceIndicator() : null;
  96.     }
  97.     public function getDefaultPriority()
  98.     {
  99.         $priorityCode $this->container->getParameter('uvdesk.default.ticket.priority');
  100.         $ticketPriority $this->entityManager->getRepository(CoreFrameworkEntity\TicketPriority::class)->findOneByCode($priorityCode);
  101.         return !empty($ticketPriority) ? $ticketPriority null;
  102.     }
  103.     public function appendTwigSnippet($snippet '')
  104.     {
  105.         switch ($snippet) {
  106.             case 'createMemberTicket':
  107.                 return $this->getMemberCreateTicketSnippet();
  108.                 break;
  109.             default:
  110.                 break;
  111.         }
  112.         return '';
  113.     }
  114.     public function getMemberCreateTicketSnippet()
  115.     {
  116.         $twigTemplatingEngine $this->container->get('twig');
  117.         $ticketTypeCollection $this->entityManager->getRepository(CoreFrameworkEntity\TicketType::class)->findByIsActive(true);
  118.         try {
  119.             if ($this->userService->isFileExists('apps/uvdesk/custom-fields')) {
  120.                 $headerCustomFields $this->container->get('uvdesk_package_custom_fields.service')->getCustomFieldsArray('user');
  121.             } else if ($this->userService->isFileExists('apps/uvdesk/form-component')) {
  122.                 $headerCustomFields $this->container->get('uvdesk_package_form_component.service')->getCustomFieldsArray('user');
  123.             }
  124.         } catch (\Exception $e) {
  125.             // @TODO: Log exception message
  126.         }
  127.         return $twigTemplatingEngine->render('@UVDeskCoreFramework/Snippets/createMemberTicket.html.twig', [
  128.             'ticketTypeCollection' => $ticketTypeCollection,
  129.             'headerCustomFields'   => $headerCustomFields ?? null,
  130.         ]);
  131.     }
  132.     public function getCustomerCreateTicketCustomFieldSnippet()
  133.     {
  134.         try {
  135.             if ($this->userService->isFileExists('apps/uvdesk/custom-fields')) {
  136.                 $customFields $this->container->get('uvdesk_package_custom_fields.service')->getCustomFieldsArray('customer');
  137.             } else if ($this->userService->isFileExists('apps/uvdesk/form-component')) {
  138.                 $customFields $this->container->get('uvdesk_package_form_component.service')->getCustomFieldsArray('customer');
  139.             }
  140.         } catch (\Exception $e) {
  141.             // @TODO: Log exception message
  142.         }
  143.         return $customFields ?? null;
  144.     }
  145.     public function createTicket(array $params = [])
  146.     {
  147.         $thread $this->entityManager->getRepository(CoreFrameworkEntity\Thread::class)->findOneByMessageId($params['messageId']);
  148.         if (empty($thread)) {
  149.             $user $this->entityManager->getRepository(CoreFrameworkEntity\User::class)->findOneByEmail($params['from']);
  150.             if (empty($user) || null == $user->getCustomerInstance()) {
  151.                 $role $this->entityManager->getRepository(CoreFrameworkEntity\SupportRole::class)->findOneByCode($params['role']);
  152.                 if (empty($role)) {
  153.                     throw new \Exception("The requested role '" $params['role'] . "' does not exist.");
  154.                 }
  155.                 // Create CoreFrameworkEntity\User Instance
  156.                 $user $this->container->get('user.service')->createUserInstance($params['from'], $params['name'], $role, [
  157.                     'source' => strtolower($params['source']),
  158.                     'active' => true,
  159.                 ]);
  160.             }
  161.             $params['role'] = 4;
  162.             $params['mailboxEmail'] = current($params['replyTo']);
  163.             $params['customer'] = $params['user'] = $user;
  164.             return $this->createTicketBase($params);
  165.         }
  166.         return;
  167.     }
  168.     public function getDemanedFilterOptions($filterType$ids)
  169.     {
  170.         $qb $this->entityManager->createQueryBuilder();
  171.         switch ($filterType) {
  172.             case 'agent':
  173.                 $qb->select("u.id,u.email,CONCAT(u.firstName,' ', u.lastName) AS name")->from(CoreFrameworkEntity\User::class, 'u')
  174.                     ->leftJoin(CoreFrameworkEntity\UserInstance::class, 'ud''WITH''u.id = ud.user')
  175.                     ->where('ud.supportRole != :roles')
  176.                     ->andwhere('ud.isActive = 1')
  177.                     ->andwhere('u.id IN (:ids)')
  178.                     ->setParameter('roles'4)
  179.                     ->setParameter('ids'$ids)
  180.                     ->orderBy('name''ASC');
  181.                 return $qb->getQuery()->getArrayResult();
  182.             case 'customer':
  183.                 $qb->select("c.id,c.email,CONCAT(c.firstName,' ', c.lastName) AS name")->from(CoreFrameworkEntity\User::class, 'c')
  184.                     ->leftJoin(CoreFrameworkEntity\UserInstance::class, 'ud''WITH''c.id = ud.user')
  185.                     ->where('ud.supportRole = :roles')
  186.                     ->andwhere('ud.isActive = 1')
  187.                     ->andwhere('c.id IN (:ids)')
  188.                     ->setParameter('roles'4)
  189.                     ->setParameter('ids'$ids)
  190.                     ->orderBy('name''ASC');
  191.                 return $qb->getQuery()->getArrayResult();
  192.             case 'group':
  193.                 $qb->select("ug.id,ug.description")->from(CoreFrameworkEntity\SupportGroup::class, 'ug')
  194.                     ->andwhere('ug.isEnabled = 1')
  195.                     ->andwhere('ug.id IN (:ids)')
  196.                     ->setParameter('ids'$ids)
  197.                     ->orderBy('ug.description''ASC');
  198.                 return $qb->getQuery()->getArrayResult();
  199.             case 'team':
  200.                 $qb->select("usg.id,usg.description")->from(CoreFrameworkEntity\SupportTeam::class, 'usg')
  201.                     ->andwhere('usg.isActive = 1')
  202.                     ->andwhere('usg.id IN (:ids)')
  203.                     ->setParameter('ids'$ids)
  204.                     ->orderBy('usg.description''ASC');
  205.                 return $qb->getQuery()->getArrayResult();
  206.             case 'tag':
  207.                 $qb->select("t.id,t.name")->from(CoreFrameworkEntity\Tag::class, 't')
  208.                     ->andwhere('t.id IN (:ids)')
  209.                     ->setParameter('ids'$ids)
  210.                     ->orderBy('t.name''ASC');
  211.                 return $qb->getQuery()->getArrayResult();
  212.         }
  213.     }
  214.     public function createTicketBase(array $ticketData = [])
  215.     {
  216.         if ('email' == $ticketData['source']) {
  217.             try {
  218.                 if (array_key_exists('UVDeskMailboxBundle'$this->container->getParameter('kernel.bundles'))) {
  219.                     $mailbox $this->mailboxService->getMailboxByEmail($ticketData['mailboxEmail']);
  220.                     $ticketData['mailboxEmail'] = $mailbox['email'];
  221.                 }
  222.             } catch (\Exception $e) {
  223.                 // No mailbox found for this email. Skip ticket creation.
  224.                 return;
  225.             }
  226.         }
  227.         // Set Defaults
  228.         $ticketType = !empty($ticketData['type']) ? $ticketData['type'] : $this->getDefaultType();
  229.         $ticketStatus = !empty($ticketData['status']) ? $ticketData['status'] : $this->getDefaultStatus();
  230.         $ticketPriority = !empty($ticketData['priority']) ? $ticketData['priority'] : $this->getDefaultPriority();
  231.         if ('email' == $ticketData['source']) {
  232.             $ticketMessageId = !empty($ticketData['messageId']) ? $ticketData['messageId'] : null;
  233.         } else {
  234.             $ticketMessageId $this->generateRandomEmailReferenceId();
  235.         }
  236.         $ticketData['type'] = $ticketType;
  237.         $ticketData['status'] = $ticketStatus;
  238.         $ticketData['priority'] = $ticketPriority;
  239.         $ticketData['messageId'] = $ticketMessageId;
  240.         $ticketData['isTrashed'] = false;
  241.         $ticket = new CoreFrameworkEntity\Ticket();
  242.         foreach ($ticketData as $property => $value) {
  243.             $callable 'set' ucwords($property);
  244.             if (method_exists($ticket$callable)) {
  245.                 $ticket->$callable($value);
  246.             }
  247.         }
  248.         $this->entityManager->persist($ticket);
  249.         $this->entityManager->flush();
  250.         return $this->createThread($ticket$ticketData);
  251.     }
  252.     public function createThread(CoreFrameworkEntity\Ticket $ticket, array $threadData)
  253.     {
  254.         $threadData['isLocked'] = 0;
  255.         if ('forward' === $threadData['threadType']) {
  256.             $threadData['replyTo'] = $threadData['to'];
  257.         }
  258.         $collaboratorEmails = [];
  259.         // check if $threadData['cc'] is not empty then merge it with $collaboratorEmails
  260.         if (! empty($threadData['cc'])) {
  261.             if (! is_array($threadData['cc'])) {
  262.                 $threadData['cc'] = [$threadData['cc']];
  263.             }
  264.             $collaboratorEmails array_merge($collaboratorEmails$threadData['cc']);
  265.         }
  266.         // check if $threadData['cccol'] is not empty
  267.         if (! empty($threadData['cccol'])) {
  268.             if (! is_array($threadData['cccol'])) {
  269.                 $threadData['cccol'] = [$threadData['cccol']];
  270.             }
  271.             $collaboratorEmails array_merge($collaboratorEmails$threadData['cccol']);
  272.         }
  273.         if (! empty($collaboratorEmails)) {
  274.             $threadData['cc'] = $collaboratorEmails;
  275.         }
  276.         $thread = new CoreFrameworkEntity\Thread();
  277.         $thread->setTicket($ticket);
  278.         $thread->setCreatedAt(new \DateTime());
  279.         $thread->setUpdatedAt(new \DateTime());
  280.         if ($threadData['message']) {
  281.             $threadData['message'] = htmlspecialchars($this->sanitizeMessage($threadData['message']), ENT_QUOTES'UTF-8');
  282.         }
  283.         if ($threadData['threadType'] != "note") {
  284.             foreach ($threadData as $property => $value) {
  285.                 if (!empty($value)) {
  286.                     $callable 'set' ucwords($property);
  287.                     if (method_exists($thread$callable)) {
  288.                         $thread->$callable($value);
  289.                     }
  290.                 }
  291.             }
  292.         } else {
  293.             $this->setTicketNotePlaceholderValue($thread$threadData$ticket);
  294.         }
  295.         // Update ticket reference ids is thread message id is defined
  296.         if (null != $thread->getMessageId() && false === strpos($ticket->getReferenceIds(), $thread->getMessageId())) {
  297.             $updatedReferenceIds $ticket->getReferenceIds() . ' ' $thread->getMessageId();
  298.             $ticket->setReferenceIds($updatedReferenceIds);
  299.             $this->entityManager->persist($ticket);
  300.         }
  301.         if ('reply' === $threadData['threadType']) {
  302.             if ('agent' === $threadData['createdBy']) {
  303.                 // Ticket has been updated by support agents, mark as agent replied | customer view pending
  304.                 $ticket->setIsCustomerViewed(false);
  305.                 $ticket->setIsReplied(true);
  306.                 $customerName $ticket->getCustomer()->getFirstName() . ' ' $ticket->getCustomer()->getLastName();
  307.                 $agentActivity = new CoreFrameworkEntity\AgentActivity();
  308.                 $agentActivity->setThreadType('reply');
  309.                 $agentActivity->setTicket($ticket);
  310.                 $agentActivity->setAgent($thread->getUser());
  311.                 $agentActivity->setCustomerName($customerName);
  312.                 $agentActivity->setAgentName('agent');
  313.                 $agentActivity->setCreatedAt(new \DateTime());
  314.                 $this->entityManager->persist($agentActivity);
  315.             } else {
  316.                 // Ticket has been updated by customer, mark as agent view | reply pending
  317.                 $ticket->setIsAgentViewed(false);
  318.                 $ticket->setIsReplied(false);
  319.             }
  320.             $this->entityManager->persist($ticket);
  321.         } else if ('create' === $threadData['threadType']) {
  322.             $ticket->setIsReplied(false);
  323.             $this->entityManager->persist($ticket);
  324.             $customerName $ticket->getCustomer()->getFirstName() . ' ' $ticket->getCustomer()->getLastName();
  325.             $agentActivity = new CoreFrameworkEntity\AgentActivity();
  326.             $agentActivity->setThreadType('create');
  327.             $agentActivity->setTicket($ticket);
  328.             $agentActivity->setAgent($thread->getUser());
  329.             $agentActivity->setCustomerName($customerName);
  330.             $agentActivity->setAgentName('agent');
  331.             $agentActivity->setCreatedAt(new \DateTime());
  332.             $this->entityManager->persist($agentActivity);
  333.         }
  334.         $ticket->currentThread $this->entityManager->getRepository(CoreFrameworkEntity\Thread::class)->getTicketCurrentThread($ticket);
  335.         $this->entityManager->persist($thread);
  336.         $this->entityManager->flush();
  337.         $ticket->createdThread $thread;
  338.         // Uploading Attachments.
  339.         if (
  340.             (isset($threadData['attachments']) && ! empty($threadData['attachments'])) || (isset($threadData['attachmentContent']) && ! empty($threadData['attachmentContent']))
  341.         ) {
  342.             if ('email' == $threadData['source']) {
  343.                 // Saving Email attachments in case of outlook with $threadData['attachmentContent']
  344.                 try {
  345.                     $attachments = ! empty($threadData['attachments']) ? $threadData['attachments'] : $threadData['attachmentContent'];
  346.                     if (! empty($attachments)) {
  347.                         $this->saveThreadEmailAttachments($thread$threadData['attachments'], $threadData['attachmentContent'] ?? []);
  348.                     }
  349.                 } catch (\Exception $e) {
  350.                     throw new \Exception($e->getMessage());
  351.                 }
  352.             } else if (!empty($threadData['attachments'])) {
  353.                 try {
  354.                     $this->fileUploadService->validateAttachments($threadData['attachments']);
  355.                     $this->saveThreadAttachment($thread$threadData['attachments']);
  356.                 } catch (\Exception $e) {
  357.                     throw new \Exception($e->getMessage());
  358.                 }
  359.             }
  360.         }
  361.         // Send Webhook Notification
  362.         $this->sendWebhookNotificationAction($thread);
  363.         return $thread;
  364.     }
  365.     public function setTicketNotePlaceholderValue($thread$threadData$ticket)
  366.     {
  367.         if (!empty($threadData)) {
  368.             foreach ($threadData as $property => $value) {
  369.                 if (!empty($value)) {
  370.                     $callable 'set' ucwords($property);
  371.                     if (method_exists($thread$callable)) {
  372.                         if ($callable != "setMessage") {
  373.                             $thread->$callable($value);
  374.                         } else {
  375.                             $notesPlaceholders $this->getNotePlaceholderValues($ticket'customer');
  376.                             $content $value;
  377.                             foreach ($notesPlaceholders as $key => $val) {
  378.                                 if (strpos($value"{%$key%}") !== false) {
  379.                                     $content strtr($value, ["{%$key%}" => $val"{% $key %}" => $val]);
  380.                                 }
  381.                             }
  382.                             $content stripslashes($content);
  383.                             $thread->$callable($content);
  384.                         }
  385.                     }
  386.                 }
  387.             }
  388.         }
  389.     }
  390.     public function saveThreadAttachment($thread, array $attachments)
  391.     {
  392.         $prefix 'threads/' $thread->getId();
  393.         $uploadManager $this->container->get('uvdesk.core.file_system.service')->getUploadManager();
  394.         foreach ($attachments as $attachment) {
  395.             $uploadedFileAttributes $uploadManager->uploadFile($attachment$prefix);
  396.             if (!empty($uploadedFileAttributes['path'])) {
  397.                 ($threadAttachment = new CoreFrameworkEntity\Attachment())
  398.                     ->setThread($thread)
  399.                     ->setName($uploadedFileAttributes['name'])
  400.                     ->setPath($uploadedFileAttributes['path'])
  401.                     ->setSize($uploadedFileAttributes['size'])
  402.                     ->setContentType($uploadedFileAttributes['content-type']);
  403.                 $this->entityManager->persist($threadAttachment);
  404.             }
  405.         }
  406.         $this->entityManager->flush();
  407.     }
  408.     public function saveThreadEmailAttachments($thread, array $attachments, array $attachmentContents)
  409.     {
  410.         $prefix 'threads/' $thread->getId();
  411.         $uploadManager $this->container->get('uvdesk.core.file_system.service')->getUploadManager();
  412.         // Upload thread attachments
  413.         foreach ($attachments as $attachment) {
  414.             $uploadedFileAttributes $uploadManager->uploadEmailAttachment($attachment$prefix);
  415.             if (!empty($uploadedFileAttributes['path'])) {
  416.                 ($threadAttachment = new CoreFrameworkEntity\Attachment())
  417.                     ->setThread($thread)
  418.                     ->setName($uploadedFileAttributes['name'])
  419.                     ->setPath($uploadedFileAttributes['path'])
  420.                     ->setSize($uploadedFileAttributes['size'])
  421.                     ->setContentType($uploadedFileAttributes['content-type']);
  422.                 $this->entityManager->persist($threadAttachment);
  423.             }
  424.         }
  425.         // Microsoft 365 Attachments.
  426.         $basePublicPath realpath(__DIR__ '/../../../../public') . '/';
  427.         $prefixOutlook $basePublicPath 'assets/threads/' $thread->getId() . '/';
  428.         foreach ($attachmentContents as $attachmentContent) {
  429.             $decodedData base64_decode(preg_replace('#^data:image/\w+;base64,#i'''$attachmentContent['content']));
  430.             $filePath $prefixOutlook $attachmentContent['name'];
  431.             if (!is_dir($prefixOutlook) && !is_dir($prefixOutlook)) {
  432.                 mkdir($prefixOutlook0755true);
  433.             }
  434.             $relativePath str_replace($basePublicPath''$filePath);
  435.             try {
  436.                 $result file_put_contents($filePath$decodedData);
  437.                 if ($result === false) {
  438.                     throw new \Exception("Failed to write file to $filePath");
  439.                 }
  440.             } catch (\Exception $e) {
  441.             }
  442.             if (!empty($filePath)) {
  443.                 $threadAttachment = (new CoreFrameworkEntity\Attachment())
  444.                     ->setThread($thread)
  445.                     ->setName($attachmentContent['name'])
  446.                     ->setPath($relativePath)
  447.                     ->setSize(strlen($decodedData)) // Use actual file size
  448.                     ->setContentType($attachmentContent['mimeType']);
  449.                 $this->entityManager->persist($threadAttachment);
  450.             }
  451.         }
  452.         $this->entityManager->flush();
  453.     }
  454.     public function getTypes()
  455.     {
  456.         static $types;
  457.         if (null !== $types)
  458.             return $types;
  459.         $qb $this->entityManager->createQueryBuilder();
  460.         $qb->select('tp.id''tp.code As name')->from(CoreFrameworkEntity\TicketType::class, 'tp')
  461.             ->andWhere('tp.isActive = 1')
  462.             ->orderBy('tp.code''ASC');
  463.         return $types $qb->getQuery()->getArrayResult();
  464.     }
  465.     public function getStatus()
  466.     {
  467.         static $statuses;
  468.         if (null !== $statuses)
  469.             return $statuses;
  470.         $qb $this->entityManager->createQueryBuilder();
  471.         $qb->select('ts')->from(CoreFrameworkEntity\TicketStatus::class, 'ts');
  472.         // $qb->orderBy('ts.sortOrder', Criteria::ASC);
  473.         return $statuses $qb->getQuery()->getArrayResult();
  474.     }
  475.     public function getTicketTotalThreads($ticketId)
  476.     {
  477.         $qb $this->entityManager->createQueryBuilder();
  478.         $qb->select('COUNT(th.id) as threadCount')->from(CoreFrameworkEntity\Ticket::class, 't')
  479.             ->leftJoin('t.threads''th')
  480.             ->andWhere('t.id = :ticketId')
  481.             ->andWhere('th.threadType = :threadType')
  482.             ->setParameter('threadType''reply')
  483.             ->setParameter('ticketId'$ticketId);
  484.         $qb $this->entityManager->createQueryBuilder();
  485.         $qb->select('COUNT(t.id) as threadCount')->from(CoreFrameworkEntity\Thread::class, 't')
  486.             ->andWhere('t.ticket = :ticketId')
  487.             ->andWhere('t.threadType = :threadType')
  488.             ->setParameter('threadType''reply')
  489.             ->setParameter('ticketId'$ticketId);
  490.         return $qb->getQuery()->getSingleScalarResult();
  491.     }
  492.     public function getTicketTags($request null)
  493.     {
  494.         $qb $this->entityManager->createQueryBuilder();
  495.         $qb->select('tg')->from(CoreFrameworkEntity\Tag::class, 'tg');
  496.         if ($request) {
  497.             $qb->andWhere("tg.name LIKE :tagName");
  498.             $qb->setParameter('tagName''%' urldecode(trim($request->query->get('query'))) . '%');
  499.             $qb->andWhere("tg.id NOT IN (:ids)");
  500.             $qb->setParameter('ids'explode(','urldecode($request->query->get('not'))));
  501.         }
  502.         return $qb->getQuery()->getArrayResult();
  503.     }
  504.     public function paginateMembersTicketCollection(Request $request)
  505.     {
  506.         $params $request->query->all();
  507.         $activeUser $this->container->get('user.service')->getSessionUser();
  508.         $activeUserTimeZone $this->entityManager->getRepository(CoreFrameworkEntity\Website::class)->findOneBy(['code' => 'Knowledgebase']);
  509.         $agentTimeZone = !empty($activeUser->getTimezone()) ? $activeUser->getTimezone() : $activeUserTimeZone->getTimezone();
  510.         $agentTimeFormat = !empty($activeUser->getTimeformat()) ? $activeUser->getTimeformat() : $activeUserTimeZone->getTimeformat();
  511.         $ticketRepository $this->entityManager->getRepository(CoreFrameworkEntity\Ticket::class);
  512.         $website $this->entityManager->getRepository(CoreFrameworkEntity\Website::class)->findOneBy(['code' => 'helpdesk']);
  513.         $timeZone $website->getTimezone();
  514.         $timeFormat $website->getTimeformat();
  515.         $supportGroupReference $this->entityManager->getRepository(CoreFrameworkEntity\User::class)->getUserSupportGroupReferences($activeUser);
  516.         $supportTeamReference  $this->entityManager->getRepository(CoreFrameworkEntity\User::class)->getUserSupportTeamReferences($activeUser);
  517.         // Get base query
  518.         $baseQuery $ticketRepository->prepareBaseTicketQuery($activeUser$supportGroupReference$supportTeamReference$params);
  519.         $ticketTabs $ticketRepository->getTicketTabDetails($activeUser$supportGroupReference$supportTeamReference$params);
  520.         // Apply Pagination
  521.         $pageNumber = !empty($params['page']) ? (int) $params['page'] : 1;
  522.         $itemsLimit = !empty($params['limit']) ? (int) $params['limit'] : $ticketRepository::DEFAULT_PAGINATION_LIMIT;
  523.         if (isset($params['repliesLess']) || isset($params['repliesMore'])) {
  524.             $paginationOptions = ['wrap-queries' => true];
  525.             $paginationQuery $baseQuery->getQuery()
  526.                 ->setHydrationMode(Query::HYDRATE_ARRAY);
  527.         } else {
  528.             $paginationOptions = ['distinct' => true];
  529.             $paginationQuery $baseQuery->getQuery()
  530.                 ->setHydrationMode(Query::HYDRATE_ARRAY)
  531.                 ->setHint('knp_paginator.count', isset($params['status']) ? $ticketTabs[$params['status']] : $ticketTabs[1]);
  532.         }
  533.         $pagination $this->container->get('knp_paginator')->paginate($paginationQuery$pageNumber$itemsLimit$paginationOptions);
  534.         // Process Pagination Response
  535.         $ticketCollection = [];
  536.         $paginationParams $pagination->getParams();
  537.         $paginationData $pagination->getPaginationData();
  538.         $paginationParams['page'] = 'replacePage';
  539.         $paginationData['url'] = '#' $this->container->get('uvdesk.service')->buildPaginationQuery($paginationParams);
  540.         // $container->get('default.service')->buildSessionUrl('ticket',$queryParameters);
  541.         $ticketThreadCountQueryTemplate $this->entityManager->createQueryBuilder()
  542.             ->select('COUNT(thread.id) as threadCount')
  543.             ->from(CoreFrameworkEntity\Ticket::class, 'ticket')
  544.             ->leftJoin('ticket.threads''thread')
  545.             ->where('ticket.id = :ticketId')
  546.             ->andWhere('thread.threadType = :threadType')->setParameter('threadType''reply');
  547.         foreach ($pagination->getItems() as $ticketDetails) {
  548.             $ticket array_shift($ticketDetails);
  549.             $ticketThreadCountQuery = clone $ticketThreadCountQueryTemplate;
  550.             $ticketThreadCountQuery->setParameter('ticketId'$ticket['id']);
  551.             $totalTicketReplies = (int) $ticketThreadCountQuery->getQuery()->getSingleScalarResult();
  552.             $ticketHasAttachments false;
  553.             $dbTime $ticket['createdAt'];
  554.             $formattedTime $this->fomatTimeByPreference($dbTime$timeZone$timeFormat$agentTimeZone$agentTimeFormat);
  555.             $currentDateTime  = new \DateTime('now');
  556.             $lastReply $this->getLastReply($ticket['id']);
  557.             if ($lastReply) {
  558.                 $lastRepliedTime =
  559.                     $this->time2string($currentDateTime->getTimeStamp() - $lastReply['createdAt']->getTimeStamp());
  560.             } else {
  561.                 $lastRepliedTime =
  562.                     $this->time2string($currentDateTime->getTimeStamp() - $ticket['createdAt']->getTimeStamp());
  563.             }
  564.             $ticketResponse = [
  565.                 'id'                => $ticket['id'],
  566.                 'subject'           => $ticket['subject'],
  567.                 'isStarred'         => $ticket['isStarred'],
  568.                 'isAgentView'       => $ticket['isAgentViewed'],
  569.                 'isTrashed'         => $ticket['isTrashed'],
  570.                 'source'            => $ticket['source'],
  571.                 'group'             => $ticketDetails['groupName'],
  572.                 'team'              => $ticketDetails['teamName'],
  573.                 'priority'          => $ticket['priority'],
  574.                 'type'              => $ticketDetails['typeName'],
  575.                 'timestamp'         => $formattedTime['dateTimeZone'],
  576.                 'formatedCreatedAt' => $formattedTime['dateTimeZone']->format($formattedTime['timeFormatString']),
  577.                 'totalThreads'      => $totalTicketReplies,
  578.                 'agent'             => null,
  579.                 'customer'          => null,
  580.                 'hasAttachments'    => $ticketHasAttachments,
  581.                 'lastReplyTime'     => $lastRepliedTime
  582.             ];
  583.             if (!empty($ticketDetails['agentId'])) {
  584.                 $ticketResponse['agent'] = [
  585.                     'id' => $ticketDetails['agentId'],
  586.                     'name' => $ticketDetails['agentName'],
  587.                     'smallThumbnail' => $ticketDetails['smallThumbnail'],
  588.                 ];
  589.             }
  590.             if (!empty($ticketDetails['customerId'])) {
  591.                 $ticketResponse['customer'] = [
  592.                     'id'             => $ticketDetails['customerId'],
  593.                     'name'           => $ticketDetails['customerName'],
  594.                     'email'          => $ticketDetails['customerEmail'],
  595.                     'smallThumbnail' => $ticketDetails['customersmallThumbnail'],
  596.                 ];
  597.             }
  598.             array_push($ticketCollection$ticketResponse);
  599.         }
  600.         return [
  601.             'tickets'    => $ticketCollection,
  602.             'pagination' => $paginationData,
  603.             'tabs'       => $ticketTabs,
  604.             'labels' => [
  605.                 'predefind' => $this->getPredefindLabelDetails($activeUser$supportGroupReference$supportTeamReference$params),
  606.                 'custom'    => $this->getCustomLabelDetails($this->container),
  607.             ],
  608.         ];
  609.     }
  610.     // Convert Timestamp to day/hour/min
  611.     public function time2string($time)
  612.     {
  613.         $d floor($time 86400);
  614.         $_d = ($d 10 '0' '') . $d;
  615.         $h floor(($time $d 86400) / 3600);
  616.         $_h = ($h 10 '0' '') . $h;
  617.         $m floor(($time - ($d 86400 $h 3600)) / 60);
  618.         $_m = ($m 10 '0' '') . $m;
  619.         $s $time - ($d 86400 $h 3600 $m 60);
  620.         $_s = ($s 10 '0' '') . $s;
  621.         $time_str "0 minutes";
  622.         if ($_d != 00)
  623.             $time_str $_d " " 'days';
  624.         elseif ($_h != 00)
  625.             $time_str $_h " " 'hours';
  626.         elseif ($_m != 00)
  627.             $time_str $_m " " 'minutes';
  628.         return $time_str " " "ago";
  629.     }
  630.     public function getPredefindLabelDetails(CoreFrameworkEntity\User $currentUser, array $supportGroupIds = [], array $supportTeamIds = [], array $params = [])
  631.     {
  632.         $data = array();
  633.         $queryBuilder $this->entityManager->createQueryBuilder();
  634.         $ticketRepository $this->entityManager->getRepository(CoreFrameworkEntity\Ticket::class);
  635.         $queryBuilder->select('COUNT(DISTINCT ticket.id) as ticketCount')->from(CoreFrameworkEntity\Ticket::class, 'ticket');
  636.         // // applyFilter according to permission
  637.         $queryBuilder->where('ticket.isTrashed != 1');
  638.         $userInstance $currentUser->getAgentInstance();
  639.         if (
  640.             !empty($userInstance) &&  'ROLE_AGENT' == $userInstance->getSupportRole()->getCode()
  641.             && $userInstance->getTicketAccesslevel() != 1
  642.         ) {
  643.             $supportGroupIds implode(','$supportGroupIds);
  644.             $supportTeamIds implode(','$supportTeamIds);
  645.             if ($userInstance->getTicketAccesslevel() == 4) {
  646.                 $queryBuilder->andWhere('ticket.agent = ' $currentUser->getId());
  647.             } elseif ($userInstance->getTicketAccesslevel() == 2) {
  648.                 $query '';
  649.                 if ($supportGroupIds) {
  650.                     $query .= ' OR supportGroup.id IN(' $supportGroupIds ') ';
  651.                 }
  652.                 if ($supportTeamIds) {
  653.                     $query .= ' OR supportTeam.id IN(' $supportTeamIds ') ';
  654.                 }
  655.                 $queryBuilder->leftJoin('ticket.supportGroup''supportGroup')
  656.                     ->leftJoin('ticket.supportTeam''supportTeam')
  657.                     ->andWhere('( ticket.agent = ' $currentUser->getId() . $query ')');
  658.             } elseif ($userInstance->getTicketAccesslevel() == 3) {
  659.                 $query '';
  660.                 if ($supportTeamIds) {
  661.                     $query .= ' OR supportTeam.id IN(' $supportTeamIds ') ';
  662.                 }
  663.                 $queryBuilder->leftJoin('ticket.supportGroup''supportGroup')
  664.                     ->leftJoin('ticket.supportTeam''supportTeam')
  665.                     ->andWhere('( ticket.agent = ' $currentUser->getId() . $query ')');
  666.             }
  667.         }
  668.         // for all tickets count
  669.         $data['all'] = $queryBuilder->getQuery()->getSingleScalarResult();
  670.         // for new tickets count
  671.         $newQb = clone $queryBuilder;
  672.         $newQb->andWhere('ticket.isNew = 1');
  673.         $data['new'] = $newQb->getQuery()->getSingleScalarResult();
  674.         // for unassigned tickets count
  675.         $unassignedQb = clone $queryBuilder;
  676.         $unassignedQb->andWhere("ticket.agent is NULL");
  677.         $data['unassigned'] = $unassignedQb->getQuery()->getSingleScalarResult();
  678.         // for unanswered ticket count
  679.         $unansweredQb = clone $queryBuilder;
  680.         $unansweredQb->andWhere('ticket.isReplied = 0');
  681.         $data['notreplied'] = $unansweredQb->getQuery()->getSingleScalarResult();
  682.         // for my tickets count
  683.         $mineQb = clone $queryBuilder;
  684.         $mineQb->andWhere("ticket.agent = :agentId")
  685.             ->setParameter('agentId'$currentUser->getId());
  686.         $data['mine'] = $mineQb->getQuery()->getSingleScalarResult();
  687.         // for starred tickets count
  688.         $starredQb = clone $queryBuilder;
  689.         $starredQb->andWhere('ticket.isStarred = 1');
  690.         $data['starred'] = $starredQb->getQuery()->getSingleScalarResult();
  691.         // for trashed tickets count
  692.         $trashedQb = clone $queryBuilder;
  693.         $trashedQb->where('ticket.isTrashed = 1');
  694.         if ($currentUser->getRoles()[0] != 'ROLE_SUPER_ADMIN' && $userInstance->getTicketAccesslevel() != 1) {
  695.             $trashedQb->andWhere('ticket.agent = ' $currentUser->getId());
  696.         }
  697.         $data['trashed'] = $trashedQb->getQuery()->getSingleScalarResult();
  698.         return $data;
  699.     }
  700.     public function paginateMembersTicketThreadCollection(CoreFrameworkEntity\Ticket $ticketRequest $request)
  701.     {
  702.         $params $request->query->all();
  703.         $entityManager $this->entityManager;
  704.         $activeUser $this->container->get('user.service')->getSessionUser();
  705.         $activeUserTimeZone $this->entityManager->getRepository(CoreFrameworkEntity\Website::class)->findOneBy(['code' => 'Knowledgebase']);
  706.         $agentTimeZone = !empty($activeUser->getTimezone()) ? $activeUser->getTimezone() : $activeUserTimeZone->getTimezone();
  707.         $agentTimeFormat = !empty($activeUser->getTimeformat()) ? $activeUser->getTimeformat() : $activeUserTimeZone->getTimeformat();
  708.         $threadRepository $entityManager->getRepository(CoreFrameworkEntity\Thread::class);
  709.         $uvdeskFileSystemService $this->container->get('uvdesk.core.file_system.service');
  710.         // Get base query
  711.         $enableLockedThreads $this->container->get('user.service')->isAccessAuthorized('ROLE_AGENT_MANAGE_LOCK_AND_UNLOCK_THREAD');
  712.         $baseQuery $threadRepository->prepareBasePaginationRecentThreadsQuery($ticket$params$enableLockedThreads);
  713.         // Apply Pagination
  714.         $paginationItemsQuery = clone $baseQuery;
  715.         $totalPaginationItems $paginationItemsQuery->select('COUNT(DISTINCT thread.id)')->getQuery()->getSingleScalarResult();
  716.         $pageNumber = !empty($params['page']) ? (int) $params['page'] : 1;
  717.         $itemsLimit = !empty($params['limit']) ? (int) $params['limit'] : $threadRepository::DEFAULT_PAGINATION_LIMIT;
  718.         $paginationOptions = ['distinct' => true];
  719.         $paginationQuery $baseQuery->getQuery()->setHydrationMode(Query::HYDRATE_ARRAY)->setHint('knp_paginator.count', (int) $totalPaginationItems);
  720.         $pagination $this->container->get('knp_paginator')->paginate($paginationQuery$pageNumber$itemsLimit$paginationOptions);
  721.         // Process Pagination Response
  722.         $threadCollection = [];
  723.         $paginationParams $pagination->getParams();
  724.         $paginationData $pagination->getPaginationData();
  725.         $website $this->entityManager->getRepository(CoreFrameworkEntity\Website::class)->findOneBy(['code' => 'helpdesk']);
  726.         $timeZone $website->getTimezone();
  727.         $timeFormat $website->getTimeformat();
  728.         if (!empty($params['threadRequestedId'])) {
  729.             $requestedThreadCollection $baseQuery
  730.                 ->andWhere('thread.id >= :threadRequestedId')->setParameter('threadRequestedId', (int) $params['threadRequestedId'])
  731.                 ->getQuery()->getArrayResult();
  732.             $totalRequestedThreads count($requestedThreadCollection);
  733.             $paginationData['current'] = ceil($totalRequestedThreads $threadRepository::DEFAULT_PAGINATION_LIMIT);
  734.             if ($paginationData['current'] > 1) {
  735.                 $paginationData['firstItemNumber'] = 1;
  736.                 $paginationData['lastItemNumber'] = $totalRequestedThreads;
  737.                 $paginationData['next'] = ceil(($totalRequestedThreads 1) / $threadRepository::DEFAULT_PAGINATION_LIMIT);
  738.             }
  739.         }
  740.         $paginationParams['page'] = 'replacePage';
  741.         $paginationData['url'] = '#' $this->container->get('uvdesk.service')->buildPaginationQuery($paginationParams);
  742.         foreach ($pagination->getItems() as $threadDetails) {
  743.             $dbTime $threadDetails['createdAt'];
  744.             $formattedTime $this->fomatTimeByPreference($dbTime$timeZone$timeFormat$agentTimeZone$agentTimeFormat);
  745.             $threadResponse = [
  746.                 'id'                => $threadDetails['id'],
  747.                 'user'              => null,
  748.                 'fullname'          => null,
  749.                 'reply'             => html_entity_decode($threadDetails['message']),
  750.                 'source'            => $threadDetails['source'],
  751.                 'threadType'        => $threadDetails['threadType'],
  752.                 'userType'          => $threadDetails['createdBy'],
  753.                 'timestamp'         => $formattedTime['dateTimeZone'],
  754.                 'formatedCreatedAt' => $formattedTime['dateTimeZone']->format($formattedTime['timeFormatString']),
  755.                 'bookmark'          => $threadDetails['isBookmarked'],
  756.                 'isLocked'          => $threadDetails['isLocked'],
  757.                 'replyTo'           => $threadDetails['replyTo'],
  758.                 'cc'                => $threadDetails['cc'],
  759.                 'bcc'               => $threadDetails['bcc'],
  760.                 'attachments'       => $threadDetails['attachments'],
  761.             ];
  762.             if (! empty($threadDetails['user'])) {
  763.                 if (!empty(trim($threadDetails['user']['firstName']))) {
  764.                     $threadResponse['fullname'] = trim($threadDetails['user']['firstName'] . ' ' $threadDetails['user']['lastName']);
  765.                 }
  766.                 $threadResponse['user'] = [
  767.                     'id' => $threadDetails['user']['id'],
  768.                     'smallThumbnail' => $threadDetails['user']['userInstance'][0]['profileImagePath'],
  769.                     'name' => $threadResponse['fullname'],
  770.                 ];
  771.             } else {
  772.                 $threadResponse['fullname'] = 'System';
  773.             }
  774.             if (!empty($threadResponse['attachments'])) {
  775.                 $threadResponse['attachments'] = array_map(function ($attachment) use ($entityManager$uvdeskFileSystemService) {
  776.                     $attachmentReferenceObject $entityManager->getReference(CoreFrameworkEntity\Attachment::class, $attachment['id']);
  777.                     return $uvdeskFileSystemService->getFileTypeAssociations($attachmentReferenceObject);
  778.                 }, $threadResponse['attachments']);
  779.             }
  780.             array_push($threadCollection$threadResponse);
  781.         }
  782.         return [
  783.             'threads'    => $threadCollection,
  784.             'pagination' => $paginationData,
  785.         ];
  786.     }
  787.     public function massXhrUpdate(Request $request)
  788.     {
  789.         $params $request->request->get('data');
  790.         foreach ($params['ids'] as $ticketId) {
  791.             $ticket $this->entityManager->getRepository(CoreFrameworkEntity\Ticket::class)->find($ticketId);
  792.             if (false == $this->isTicketAccessGranted($ticket)) {
  793.                 throw new \Exception('Access Denied'403);
  794.             }
  795.             if (empty($ticket)) {
  796.                 continue;
  797.             }
  798.             switch ($params['actionType']) {
  799.                 case 'trashed':
  800.                     if (false == $ticket->getIsTrashed()) {
  801.                         $ticket->setIsTrashed(true);
  802.                         $this->entityManager->persist($ticket);
  803.                     }
  804.                     // Trigger ticket delete event
  805.                     $event = new CoreWorkflowEvents\Ticket\Delete();
  806.                     $event
  807.                         ->setTicket($ticket);
  808.                     $this->container->get('event_dispatcher')->dispatch($event'uvdesk.automation.workflow.execute');
  809.                     break;
  810.                 case 'delete':
  811.                     $threads $ticket->getThreads();
  812.                     $fileService = new Filesystem();
  813.                     if (count($threads) > 0) {
  814.                         foreach ($threads as $thread) {
  815.                             if (!empty($thread)) {
  816.                                 $fileService->remove($this->container->getParameter('kernel.project_dir') . '/public/assets/threads/' $thread->getId());
  817.                             }
  818.                         }
  819.                     }
  820.                     $this->entityManager->remove($ticket);
  821.                     break;
  822.                 case 'restored':
  823.                     if (true == $ticket->getIsTrashed()) {
  824.                         $ticket->setIsTrashed(false);
  825.                         $this->entityManager->persist($ticket);
  826.                     }
  827.                     break;
  828.                 case 'agent':
  829.                     if ($ticket->getAgent() == null || $ticket->getAgent() && $ticket->getAgent()->getId() != $params['targetId']) {
  830.                         $agent $this->entityManager->getRepository(CoreFrameworkEntity\User::class)->find($params['targetId']);
  831.                         $ticket->setAgent($agent);
  832.                         $this->entityManager->persist($ticket);
  833.                         // Trigger Agent Assign event
  834.                         $event = new CoreWorkflowEvents\Ticket\Agent();
  835.                         $event
  836.                             ->setTicket($ticket);
  837.                         $this->container->get('event_dispatcher')->dispatch($event'uvdesk.automation.workflow.execute');
  838.                     }
  839.                     break;
  840.                 case 'status':
  841.                     if ($ticket->getStatus() == null || $ticket->getStatus() && $ticket->getStatus()->getId() != $params['targetId']) {
  842.                         $status $this->entityManager->getRepository(CoreFrameworkEntity\TicketStatus::class)->findOneById($params['targetId']);
  843.                         $ticket->setStatus($status);
  844.                         $this->entityManager->persist($ticket);
  845.                         // Trigger ticket status event
  846.                         $event = new CoreWorkflowEvents\Ticket\Status();
  847.                         $event
  848.                             ->setTicket($ticket);
  849.                         $this->container->get('event_dispatcher')->dispatch($event'uvdesk.automation.workflow.execute');
  850.                     }
  851.                     break;
  852.                 case 'type':
  853.                     if ($ticket->getType() == null || $ticket->getType() && $ticket->getType()->getId() != $params['targetId']) {
  854.                         $type $this->entityManager->getRepository(CoreFrameworkEntity\TicketType::class)->findOneById($params['targetId']);
  855.                         $ticket->setType($type);
  856.                         $this->entityManager->persist($ticket);
  857.                         // Trigger ticket type event
  858.                         $event = new CoreWorkflowEvents\Ticket\Type();
  859.                         $event
  860.                             ->setTicket($ticket);
  861.                         $this->container->get('event_dispatcher')->dispatch($event'uvdesk.automation.workflow.execute');
  862.                     }
  863.                     break;
  864.                 case 'group':
  865.                     if ($ticket->getSupportGroup() == null || $ticket->getSupportGroup() && $ticket->getSupportGroup()->getId() != $params['targetId']) {
  866.                         $group $this->entityManager->getRepository(CoreFrameworkEntity\SupportGroup::class)->find($params['targetId']);
  867.                         $ticket->setSupportGroup($group);
  868.                         $this->entityManager->persist($ticket);
  869.                         // Trigger Support group event
  870.                         $event = new CoreWorkflowEvents\Ticket\Group();
  871.                         $event
  872.                             ->setTicket($ticket);
  873.                         $this->container->get('event_dispatcher')->dispatch($event'uvdesk.automation.workflow.execute');
  874.                     }
  875.                     break;
  876.                 case 'team':
  877.                     if ($ticket->getSupportTeam() == null || $ticket->getSupportTeam() && $ticket->getSupportTeam()->getId() != $params['targetId']) {
  878.                         $team $this->entityManager->getRepository(CoreFrameworkEntity\SupportTeam::class)->find($params['targetId']);
  879.                         $ticket->setSupportTeam($team);
  880.                         $this->entityManager->persist($ticket);
  881.                         // Trigger team event
  882.                         $event = new CoreWorkflowEvents\Ticket\Team();
  883.                         $event
  884.                             ->setTicket($ticket);
  885.                         $this->container->get('event_dispatcher')->dispatch($event'uvdesk.automation.workflow.execute');
  886.                     }
  887.                     break;
  888.                 case 'priority':
  889.                     if ($ticket->getPriority() == null || $ticket->getPriority() && $ticket->getPriority()->getId() != $params['targetId']) {
  890.                         $priority $this->entityManager->getRepository(CoreFrameworkEntity\TicketPriority::class)->find($params['targetId']);
  891.                         $ticket->setPriority($priority);
  892.                         $this->entityManager->persist($ticket);
  893.                         // Trigger ticket Priority event
  894.                         $event = new CoreWorkflowEvents\Ticket\Priority();
  895.                         $event
  896.                             ->setTicket($ticket);
  897.                         $this->container->get('event_dispatcher')->dispatch($event'uvdesk.automation.workflow.execute');
  898.                     }
  899.                     break;
  900.                 case 'label':
  901.                     $label $this->entityManager->getRepository(CoreFrameworkEntity\SupportLabel::class)->find($params['targetId']);
  902.                     if ($label && !$this->entityManager->getRepository(CoreFrameworkEntity\Ticket::class)->isLabelAlreadyAdded($ticket$label)) {
  903.                         $ticket->addSupportLabel($label);
  904.                     }
  905.                     $this->entityManager->persist($ticket);
  906.                     break;
  907.                 default:
  908.                     break;
  909.             }
  910.         }
  911.         $this->entityManager->flush();
  912.         if ($params['actionType'] == 'trashed') {
  913.             $message 'Success ! Tickets moved to trashed successfully.';
  914.         } elseif ($params['actionType'] == 'restored') {
  915.             $message 'Success ! Tickets restored successfully.';
  916.         } elseif ($params['actionType'] == 'delete') {
  917.             $message 'Success ! Tickets removed successfully.';
  918.         } elseif ($params['actionType'] == 'agent') {
  919.             $message 'Success ! Agent assigned successfully.';
  920.         } elseif ($params['actionType'] == 'status') {
  921.             $message 'Success ! Tickets status updated successfully.';
  922.         } elseif ($params['actionType'] == 'type') {
  923.             $message 'Success ! Tickets type updated successfully.';
  924.         } elseif ($params['actionType'] == 'group') {
  925.             $message 'Success ! Tickets group updated successfully.';
  926.         } elseif ($params['actionType'] == 'team') {
  927.             $message 'Success ! Tickets team updated successfully.';
  928.         } elseif ($params['actionType'] == 'priority') {
  929.             $message 'Success ! Tickets priority updated successfully.';
  930.         } elseif ($params['actionType'] == 'label') {
  931.             $message 'Success ! Tickets added to label successfully.';
  932.         } else {
  933.             $message 'Success ! Tickets have been updated successfully';
  934.         }
  935.         return [
  936.             'alertClass' => 'success',
  937.             'alertMessage' => $this->trans($message),
  938.         ];
  939.     }
  940.     public function getNotePlaceholderValues($ticket$type "customer")
  941.     {
  942.         $variables = array();
  943.         $variables['ticket.id'] = $ticket->getId();
  944.         $variables['ticket.subject'] = $ticket->getSubject();
  945.         $variables['ticket.status'] = $ticket->getStatus()->getCode();
  946.         $variables['ticket.priority'] = $ticket->getPriority()->getCode();
  947.         if ($ticket->getSupportGroup())
  948.             $variables['ticket.group'] = $ticket->getSupportGroup()->getName();
  949.         else
  950.             $variables['ticket.group'] = '';
  951.         $variables['ticket.team'] = ($ticket->getSupportTeam() ? $ticket->getSupportTeam()->getName() : '');
  952.         $customer $this->container->get('user.service')->getCustomerPartialDetailById($ticket->getCustomer()->getId());
  953.         $variables['ticket.customerName'] = $customer['name'];
  954.         $userService $this->container->get('user.service');
  955.         $variables['ticket.agentName'] = '';
  956.         $variables['ticket.agentEmail'] = '';
  957.         if ($ticket->getAgent()) {
  958.             $agent $this->container->get('user.service')->getAgentDetailById($ticket->getAgent()->getId());
  959.             if ($agent) {
  960.                 $variables['ticket.agentName'] = $agent['name'];
  961.                 $variables['ticket.agentEmail'] = $agent['email'];
  962.             }
  963.         }
  964.         $router $this->container->get('router');
  965.         if ($type == 'customer') {
  966.             $ticketListURL $router->generate('helpdesk_member_ticket_collection', [
  967.                 'id' => $ticket->getId(),
  968.             ], UrlGeneratorInterface::ABSOLUTE_URL);
  969.         } else {
  970.             $ticketListURL $router->generate('helpdesk_customer_ticket_collection', [
  971.                 'id' => $ticket->getId(),
  972.             ], UrlGeneratorInterface::ABSOLUTE_URL);
  973.         }
  974.         $variables['ticket.link'] = sprintf("<a href='%s'>#%s</a>"$ticketListURL$ticket->getId());
  975.         return $variables;
  976.     }
  977.     public function paginateMembersTicketTypeCollection(Request $request)
  978.     {
  979.         // Get base query
  980.         $params $request->query->all();
  981.         $ticketRepository $this->entityManager->getRepository(CoreFrameworkEntity\Ticket::class);
  982.         $paginationQuery $ticketRepository->prepareBasePaginationTicketTypesQuery($params);
  983.         // Apply Pagination
  984.         $paginationOptions = ['distinct' => true];
  985.         $pageNumber = !empty($params['page']) ? (int) $params['page'] : 1;
  986.         $itemsLimit = !empty($params['limit']) ? (int) $params['limit'] : $ticketRepository::DEFAULT_PAGINATION_LIMIT;
  987.         $pagination $this->container->get('knp_paginator')->paginate($paginationQuery$pageNumber$itemsLimit$paginationOptions);
  988.         // Process Pagination Response
  989.         $paginationParams $pagination->getParams();
  990.         $paginationData $pagination->getPaginationData();
  991.         $paginationParams['page'] = 'replacePage';
  992.         $paginationData['url'] = '#' $this->container->get('uvdesk.service')->buildPaginationQuery($paginationParams);
  993.         return [
  994.             'types' => array_map(function ($ticketType) {
  995.                 return [
  996.                     'id'          => $ticketType->getId(),
  997.                     'code'        => strtoupper($ticketType->getCode()),
  998.                     'description' => $ticketType->getDescription(),
  999.                     'isActive'    => $ticketType->getIsActive(),
  1000.                 ];
  1001.             }, $pagination->getItems()),
  1002.             'pagination_data' => $paginationData,
  1003.         ];
  1004.     }
  1005.     public function paginateMembersTagCollection(Request $request)
  1006.     {
  1007.         // Get base query
  1008.         $params $request->query->all();
  1009.         $ticketRepository $this->entityManager->getRepository(CoreFrameworkEntity\Ticket::class);
  1010.         $baseQuery $ticketRepository->prepareBasePaginationTagsQuery($params);
  1011.         // Apply Pagination
  1012.         $paginationResultsQuery = clone $baseQuery;
  1013.         $paginationResultsQuery->select('COUNT(supportTag.id)');
  1014.         $paginationQuery $baseQuery->getQuery()->setHydrationMode(Query::HYDRATE_ARRAY)->setHint('knp_paginator.count'count($paginationResultsQuery->getQuery()->getResult()));
  1015.         $paginationOptions = ['distinct' => true];
  1016.         $pageNumber = !empty($params['page']) ? (int) $params['page'] : 1;
  1017.         $itemsLimit = !empty($params['limit']) ? (int) $params['limit'] : $ticketRepository::DEFAULT_PAGINATION_LIMIT;
  1018.         $pagination $this->container->get('knp_paginator')->paginate($paginationQuery$pageNumber$itemsLimit$paginationOptions);
  1019.         // Process Pagination Response
  1020.         $paginationParams $pagination->getParams();
  1021.         $paginationData $pagination->getPaginationData();
  1022.         $paginationParams['page'] = 'replacePage';
  1023.         $paginationData['url'] = '#' $this->container->get('uvdesk.service')->buildPaginationQuery($paginationParams);
  1024.         if (in_array('UVDeskSupportCenterBundle'array_keys($this->container->getParameter('kernel.bundles')))) {
  1025.             $articleRepository $this->entityManager->getRepository(Article::class);
  1026.             return [
  1027.                 'tags' => array_map(function ($supportTag) use ($articleRepository) {
  1028.                     return [
  1029.                         'id'           => $supportTag['id'],
  1030.                         'name'         => $supportTag['name'],
  1031.                         'ticketCount'  => $supportTag['totalTickets'],
  1032.                         'articleCount' => $articleRepository->getTotalArticlesBySupportTag($supportTag['id']),
  1033.                     ];
  1034.                 }, $pagination->getItems()),
  1035.                 'pagination_data' => $paginationData,
  1036.             ];
  1037.         } else {
  1038.             return [
  1039.                 'tags' => array_map(function ($supportTag) {
  1040.                     return [
  1041.                         'id'          => $supportTag['id'],
  1042.                         'name'        => $supportTag['name'],
  1043.                         'ticketCount' => $supportTag['totalTickets'],
  1044.                     ];
  1045.                 }, $pagination->getItems()),
  1046.                 'pagination_data' => $paginationData,
  1047.             ];
  1048.         }
  1049.     }
  1050.     public function getTicketInitialThreadDetails(CoreFrameworkEntity\Ticket $ticket)
  1051.     {
  1052.         $initialThread $this->entityManager->getRepository(CoreFrameworkEntity\Thread::class)->findOneBy([
  1053.             'ticket'     => $ticket,
  1054.             'threadType' => 'create',
  1055.         ]);
  1056.         if (!empty($initialThread)) {
  1057.             $author $initialThread->getUser();
  1058.             $authorInstance 'agent' == $initialThread->getCreatedBy() ? $author->getAgentInstance() : $author->getCustomerInstance();
  1059.             $threadDetails = [
  1060.                 'id'          => $initialThread->getId(),
  1061.                 'source'      => $initialThread->getSource(),
  1062.                 'messageId'   => $initialThread->getMessageId(),
  1063.                 'threadType'  => $initialThread->getThreadType(),
  1064.                 'createdBy'   => $initialThread->getCreatedBy(),
  1065.                 'message'     => html_entity_decode($initialThread->getMessage()),
  1066.                 'attachments' => $initialThread->getAttachments(),
  1067.                 'timestamp'   => $initialThread->getCreatedAt()->getTimestamp(),
  1068.                 'createdAt'   => $initialThread->getCreatedAt()->format('d-m-Y h:ia'),
  1069.                 'user'        => $authorInstance->getPartialDetails(),
  1070.                 'cc'          => is_array($initialThread->getCc()) ? implode(', '$initialThread->getCc()) : '',
  1071.             ];
  1072.             $attachments $threadDetails['attachments']->getValues();
  1073.             if (!empty($attachments)) {
  1074.                 $uvdeskFileSystemService $this->container->get('uvdesk.core.file_system.service');
  1075.                 $threadDetails['attachments'] = array_map(function ($attachment) use ($uvdeskFileSystemService) {
  1076.                     return $uvdeskFileSystemService->getFileTypeAssociations($attachment);
  1077.                 }, $attachments);
  1078.             }
  1079.         }
  1080.         return $threadDetails ?? null;
  1081.     }
  1082.     public function getCreateReply($ticketId$firewall 'member')
  1083.     {
  1084.         $qb $this->entityManager->createQueryBuilder();
  1085.         $qb->select("th,a,u.id as userId")->from(CoreFrameworkEntity\Thread::class, 'th')
  1086.             ->leftJoin('th.ticket''t')
  1087.             ->leftJoin('th.attachments''a')
  1088.             ->leftJoin('th.user''u')
  1089.             ->andWhere('t.id = :ticketId')
  1090.             ->andWhere('th.threadType = :threadType')
  1091.             ->setParameter('threadType''create')
  1092.             ->setParameter('ticketId'$ticketId)
  1093.             ->orderBy('th.id''DESC')
  1094.             ->getMaxResults(1);
  1095.         $threadResponse $qb->getQuery()->getArrayResult();
  1096.         if ((!empty($threadResponse[0][0]))) {
  1097.             $threadDetails $threadResponse[0][0];
  1098.             $userService $this->container->get('user.service');
  1099.             if ($threadDetails['createdBy'] == 'agent') {
  1100.                 $threadDetails['user'] = $userService->getAgentDetailById($threadResponse[0]['userId']);
  1101.             } else {
  1102.                 $threadDetails['user'] = $userService->getCustomerPartialDetailById($threadResponse[0]['userId']);
  1103.             }
  1104.             $threadDetails['reply'] = html_entity_decode($threadDetails['message']);
  1105.             $threadDetails['formatedCreatedAt'] = $this->timeZoneConverter($threadDetails['createdAt']);
  1106.             $threadDetails['timestamp'] = $userService->convertToDatetimeTimezoneTimestamp($threadDetails['createdAt']);
  1107.             if (!empty($threadDetails['attachments'])) {
  1108.                 $entityManager $this->entityManager;
  1109.                 $uvdeskFileSystemService $this->container->get('uvdesk.core.file_system.service');
  1110.                 $threadDetails['attachments'] = array_map(function ($attachment) use ($entityManager$uvdeskFileSystemService$firewall) {
  1111.                     $attachmentReferenceObject $entityManager->getReference(CoreFrameworkEntity\Attachment::class, $attachment['id']);
  1112.                     return $uvdeskFileSystemService->getFileTypeAssociations($attachmentReferenceObject$firewall);
  1113.                 }, $threadDetails['attachments']);
  1114.             }
  1115.         }
  1116.         return $threadDetails ?? null;
  1117.     }
  1118.     public function hasAttachments($ticketId)
  1119.     {
  1120.         $qb $this->entityManager->createQueryBuilder();
  1121.         $qb->select("DISTINCT COUNT(a.id) as attachmentCount")->from(CoreFrameworkEntity\Thread::class, 'th')
  1122.             ->leftJoin('th.ticket''t')
  1123.             ->leftJoin('th.attachments''a')
  1124.             ->andWhere('t.id = :ticketId')
  1125.             ->setParameter('ticketId'$ticketId);
  1126.         return intval($qb->getQuery()->getSingleScalarResult());
  1127.     }
  1128.     public function getAgentDraftReply()
  1129.     {
  1130.         $signature $this->getUser()->getAgentInstance()->getSignature();
  1131.         return str_replace("\n"'<br/>'$signature);
  1132.     }
  1133.     public function trans($text)
  1134.     {
  1135.         return $this->container->get('translator')->trans($text);
  1136.     }
  1137.     public function getAllSources()
  1138.     {
  1139.         $sources = ['email' => 'Email''website' => 'Website'];
  1140.         return $sources;
  1141.     }
  1142.     public function getCustomLabelDetails($container)
  1143.     {
  1144.         $currentUser $container->get('user.service')->getCurrentUser();
  1145.         $qb $this->entityManager->createQueryBuilder();
  1146.         $qb->select('COUNT(DISTINCT t) as ticketCount,sl.id')->from(CoreFrameworkEntity\Ticket::class, 't')
  1147.             ->leftJoin('t.supportLabels''sl')
  1148.             ->andWhere('sl.user = :userId')
  1149.             ->setParameter('userId'$currentUser->getId())
  1150.             ->groupBy('sl.id');
  1151.         $ticketCountResult $qb->getQuery()->getResult();
  1152.         $data = array();
  1153.         $qb $this->entityManager->createQueryBuilder();
  1154.         $qb->select('sl.id,sl.name,sl.colorCode')->from(CoreFrameworkEntity\SupportLabel::class, 'sl')
  1155.             ->andWhere('sl.user = :userId')
  1156.             ->setParameter('userId'$currentUser->getId());
  1157.         $labels $qb->getQuery()->getResult();
  1158.         foreach ($labels as $key => $label) {
  1159.             $labels[$key]['count'] = 0;
  1160.             foreach ($ticketCountResult as $ticketCount) {
  1161.                 if (($label['id'] == $ticketCount['id']))
  1162.                     $labels[$key]['count'] = $ticketCount['ticketCount'] ?: 0;
  1163.             }
  1164.         }
  1165.         return $labels;
  1166.     }
  1167.     public function getLabels($request null)
  1168.     {
  1169.         static $labels;
  1170.         if (null !== $labels)
  1171.             return $labels;
  1172.         $qb $this->entityManager->createQueryBuilder();
  1173.         $qb->select('sl')->from(CoreFrameworkEntity\SupportLabel::class, 'sl')
  1174.             ->andWhere('sl.user = :userId')
  1175.             ->setParameter('userId'$this->getUser()->getId());
  1176.         if ($request) {
  1177.             $qb->andWhere("sl.name LIKE :labelName");
  1178.             $qb->setParameter('labelName''%' urldecode(trim($request->query->get('query'))) . '%');
  1179.         }
  1180.         return $labels $qb->getQuery()->getArrayResult();
  1181.     }
  1182.     public function getTicketCollaborators($ticketId)
  1183.     {
  1184.         $qb $this->entityManager->createQueryBuilder();
  1185.         $qb->select("DISTINCT c.id, c.email, CONCAT(c.firstName,' ', c.lastName) AS name, userInstance.profileImagePath, userInstance.profileImagePath as smallThumbnail")->from(CoreFrameworkEntity\Ticket::class, 't')
  1186.             ->leftJoin('t.collaborators''c')
  1187.             ->leftJoin('c.userInstance''userInstance')
  1188.             ->andWhere('t.id = :ticketId')
  1189.             ->andWhere('userInstance.supportRole = :roles')
  1190.             ->setParameter('ticketId'$ticketId)
  1191.             ->setParameter('roles'4)
  1192.             ->orderBy('name''ASC');
  1193.         return $qb->getQuery()->getArrayResult();
  1194.     }
  1195.     public function getTicketTagsById($ticketId)
  1196.     {
  1197.         $qb $this->entityManager->createQueryBuilder();
  1198.         $qb->select('tg')->from(CoreFrameworkEntity\Tag::class, 'tg')
  1199.             ->leftJoin('tg.tickets''t')
  1200.             ->andWhere('t.id = :ticketId')
  1201.             ->setParameter('ticketId'$ticketId);
  1202.         return $qb->getQuery()->getArrayResult();
  1203.     }
  1204.     public function getTicketLabels($ticketId)
  1205.     {
  1206.         $qb $this->entityManager->createQueryBuilder();
  1207.         $qb->select('DISTINCT sl.id,sl.name,sl.colorCode')->from(CoreFrameworkEntity\Ticket::class, 't')
  1208.             ->leftJoin('t.supportLabels''sl')
  1209.             ->leftJoin('sl.user''slu')
  1210.             ->andWhere('slu.id = :userId')
  1211.             ->andWhere('t.id = :ticketId')
  1212.             ->setParameter('userId'$this->getUser()->getId())
  1213.             ->setParameter('ticketId'$ticketId);
  1214.         $result $qb->getQuery()->getResult();
  1215.         return $result $result : [];
  1216.     }
  1217.     public function getUserLabels()
  1218.     {
  1219.         $qb $this->entityManager->createQueryBuilder();
  1220.         $qb->select('sl')->from(CoreFrameworkEntity\SupportLabel::class, 'sl')
  1221.             ->leftJoin('sl.user''slu')
  1222.             ->andWhere('slu.id = :userId')
  1223.             ->setParameter('userId'$this->getUser()->getId());
  1224.         $result $qb->getQuery()->getResult();
  1225.         return $result $result : [];
  1226.     }
  1227.     public function getTicketLabelsAll($ticketId)
  1228.     {
  1229.         $qb $this->entityManager->createQueryBuilder();
  1230.         $qb->select('DISTINCT sl.id,sl.name,sl.colorCode')->from(CoreFrameworkEntity\Ticket::class, 't')
  1231.             ->leftJoin('t.supportLabels''sl')
  1232.             ->andWhere('t.id = :ticketId')
  1233.             ->setParameter('ticketId'$ticketId);
  1234.         $result $qb->getQuery()->getResult();
  1235.         return $result $result : [];
  1236.     }
  1237.     public function getManualWorkflow()
  1238.     {
  1239.         $preparedResponseIds = [];
  1240.         $groupIds = [];
  1241.         $teamIds = [];
  1242.         $userId $this->container->get('user.service')->getCurrentUser()->getAgentInstance()->getId();
  1243.         $preparedResponseRepo $this->entityManager->getRepository(PreparedResponses::class)->findAll();
  1244.         foreach ($preparedResponseRepo as $pr) {
  1245.             if ($userId == $pr->getUser()->getId()) {
  1246.                 //Save the ids of the saved reply.
  1247.                 array_push($preparedResponseIds, (int)$pr->getId());
  1248.             }
  1249.         }
  1250.         // Get the ids of the Group(s) the current user is associated with.
  1251.         $query "select * from uv_user_support_groups where userInstanceId =" $userId;
  1252.         $connection $this->entityManager->getConnection();
  1253.         $stmt $connection->prepare($query);
  1254.         $result $stmt->executeQuery()->fetchAllAssociative();
  1255.         foreach ($result as $row) {
  1256.             array_push($groupIds$row['supportGroupId']);
  1257.         }
  1258.         // Get all the saved reply's ids that is associated with the user's group(s).
  1259.         $query "select * from uv_prepared_response_support_groups";
  1260.         $stmt $connection->prepare($query);
  1261.         $result $stmt->executeQuery()->fetchAllAssociative();
  1262.         foreach ($result as $row) {
  1263.             if (in_array($row['group_id'], $groupIds)) {
  1264.                 array_push($preparedResponseIds, (int) $row['savedReply_id']);
  1265.             }
  1266.         }
  1267.         // Get the ids of the Team(s) the current user is associated with.
  1268.         $query "select * from uv_user_support_teams";
  1269.         $connection $this->entityManager->getConnection();
  1270.         $stmt $connection->prepare($query);
  1271.         $result $stmt->executeQuery()->fetchAllAssociative();
  1272.         foreach ($result as $row) {
  1273.             if ($row['userInstanceId'] == $userId) {
  1274.                 array_push($teamIds$row['supportTeamId']);
  1275.             }
  1276.         }
  1277.         $query "select * from uv_prepared_response_support_teams";
  1278.         $stmt $connection->prepare($query);
  1279.         $result $stmt->executeQuery()->fetchAllAssociative();
  1280.         foreach ($result as $row) {
  1281.             if (in_array($row['subgroup_id'], $teamIds)) {
  1282.                 array_push($preparedResponseIds, (int)$row['savedReply_id']);
  1283.             }
  1284.         }
  1285.         $qb $this->entityManager->createQueryBuilder();
  1286.         $qb->select('DISTINCT mw')
  1287.             ->from(PreparedResponses::class, 'mw')
  1288.             ->where('mw.status = 1')
  1289.             ->andWhere('mw.id IN (:ids)')
  1290.             ->setParameter('ids'$preparedResponseIds);
  1291.         return $qb->getQuery()->getResult();
  1292.     }
  1293.     public function getSavedReplies()
  1294.     {
  1295.         $savedReplyIds = [];
  1296.         $groupIds = [];
  1297.         $teamIds = [];
  1298.         $userInstance $this->container->get('user.service')->getCurrentUser()->getAgentInstance();
  1299.         $userId $userInstance->getId();
  1300.         $currentRole $userInstance->getSupportRole()->getCode();
  1301.         $savedReplyRepo $this->entityManager->getRepository(CoreFrameworkEntity\SavedReplies::class)->findAll();
  1302.         if (in_array($currentRole, ['ROLE_SUPER_ADMIN''ROLE_ADMIN'])) {
  1303.             return $savedReplyRepo;
  1304.         }
  1305.         foreach ($savedReplyRepo as $sr) {
  1306.             if ($userId == $sr->getUser()->getId()) {
  1307.                 //Save the ids of the saved reply.
  1308.                 array_push($savedReplyIds, (int)$sr->getId());
  1309.             }
  1310.         }
  1311.         // Get the ids of the Group(s) the current user is associated with.
  1312.         $query "select * from uv_user_support_groups where userInstanceId = :userId";
  1313.         $connection $this->entityManager->getConnection();
  1314.         $stmt $connection->prepare($query);
  1315.         $result $stmt->executeQuery(['userId' => $userId])->fetchAllAssociative();
  1316.         foreach ($result as $row) {
  1317.             array_push($groupIds$row['supportGroupId']);
  1318.         }
  1319.         // Get all the saved reply's ids that is associated with the user's group(s).
  1320.         $query "select * from uv_saved_replies_groups";
  1321.         $stmt $connection->prepare($query);
  1322.         $result $stmt->executeQuery()->fetchAllAssociative();
  1323.         foreach ($result as $row) {
  1324.             if (in_array($row['group_id'], $groupIds)) {
  1325.                 array_push($savedReplyIds, (int) $row['savedReply_id']);
  1326.             }
  1327.         }
  1328.         // Get the ids of the Team(s) the current user is associated with.
  1329.         $query "select * from uv_user_support_teams";
  1330.         $connection $this->entityManager->getConnection();
  1331.         $stmt $connection->prepare($query);
  1332.         $result $stmt->executeQuery()->fetchAllAssociative();
  1333.         foreach ($result as $row) {
  1334.             if ($row['userInstanceId'] == $userId) {
  1335.                 array_push($teamIds$row['supportTeamId']);
  1336.             }
  1337.         }
  1338.         $query "select * from uv_saved_replies_teams";
  1339.         $stmt $connection->prepare($query);
  1340.         $result $stmt->executeQuery()->fetchAllAssociative();
  1341.         foreach ($result as $row) {
  1342.             if (in_array($row['subgroup_id'], $teamIds)) {
  1343.                 array_push($savedReplyIds, (int)$row['savedReply_id']);
  1344.             }
  1345.         }
  1346.         $qb $this->entityManager->createQueryBuilder();
  1347.         $qb->select('DISTINCT sr')
  1348.             ->from(CoreFrameworkEntity\SavedReplies::class, 'sr')
  1349.             ->Where('sr.id IN (:ids)')
  1350.             ->setParameter('ids'$savedReplyIds);
  1351.         return $qb->getQuery()->getResult();
  1352.     }
  1353.     public function getPriorities()
  1354.     {
  1355.         static $priorities;
  1356.         if (null !== $priorities)
  1357.             return $priorities;
  1358.         $qb $this->entityManager->createQueryBuilder();
  1359.         $qb->select('tp')->from(CoreFrameworkEntity\TicketPriority::class, 'tp');
  1360.         return $priorities $qb->getQuery()->getArrayResult();
  1361.     }
  1362.     public function getTicketLastThread($ticketId)
  1363.     {
  1364.         $qb $this->entityManager->createQueryBuilder();
  1365.         $qb->select("th")->from(CoreFrameworkEntity\Thread::class, 'th')
  1366.             ->leftJoin('th.ticket''t')
  1367.             ->andWhere('t.id = :ticketId')
  1368.             ->setParameter('ticketId'$ticketId)
  1369.             ->orderBy('th.id''DESC');
  1370.         return $qb->getQuery()->setMaxResults(1)->getSingleResult();
  1371.     }
  1372.     public function getlastReplyAgentName($ticketId)
  1373.     {
  1374.         $qb $this->entityManager->createQueryBuilder();
  1375.         $qb->select("u.id,CONCAT(u.firstName,' ', u.lastName) AS name,u.firstName")->from(CoreFrameworkEntity\Thread::class, 'th')
  1376.             ->leftJoin('th.ticket''t')
  1377.             ->leftJoin('th.user''u')
  1378.             ->leftJoin('u.userInstance''userInstance')
  1379.             ->andWhere('userInstance.supportRole != :roles')
  1380.             ->andWhere('t.id = :ticketId')
  1381.             ->andWhere('th.threadType = :threadType')
  1382.             ->setParameter('threadType''reply')
  1383.             ->andWhere('th.createdBy = :createdBy')
  1384.             ->setParameter('createdBy''agent')
  1385.             ->setParameter('ticketId'$ticketId)
  1386.             ->setParameter('roles'4)
  1387.             ->orderBy('th.id''DESC');
  1388.         $result $qb->getQuery()->setMaxResults(1)->getResult();
  1389.         return $result $result[0] : null;
  1390.     }
  1391.     public function getLastReply($ticketId$userType null)
  1392.     {
  1393.         $queryBuilder $this->entityManager->createQueryBuilder();
  1394.         $queryBuilder->select("th, a, u.id as userId")
  1395.             ->from(CoreFrameworkEntity\Thread::class, 'th')
  1396.             ->leftJoin('th.ticket''t')
  1397.             ->leftJoin('th.attachments''a')
  1398.             ->leftJoin('th.user''u')
  1399.             ->andWhere('t.id = :ticketId')
  1400.             ->andWhere('th.threadType = :threadType')
  1401.             ->setParameter('threadType''reply')
  1402.             ->setParameter('ticketId'$ticketId)
  1403.             ->orderBy('th.id''DESC')
  1404.             ->getMaxResults(1);
  1405.         if (!empty($userType)) {
  1406.             $queryBuilder->andWhere('th.createdBy = :createdBy')->setParameter('createdBy'$userType);
  1407.         }
  1408.         $threadResponse $queryBuilder->getQuery()->getArrayResult();
  1409.         if (!empty($threadResponse[0][0])) {
  1410.             $threadDetails $threadResponse[0][0];
  1411.             $userService $this->container->get('user.service');
  1412.             if ($threadDetails['createdBy'] == 'agent') {
  1413.                 $threadDetails['user'] = $userService->getAgentDetailById($threadResponse[0]['userId']);
  1414.             } else {
  1415.                 $threadDetails['user'] = $userService->getCustomerPartialDetailById($threadResponse[0]['userId']);
  1416.             }
  1417.             $threadDetails['reply'] = html_entity_decode($threadDetails['message']);
  1418.             $threadDetails['formatedCreatedAt'] = $this->timeZoneConverter($threadDetails['createdAt']);
  1419.             $threadDetails['timestamp'] = $userService->convertToDatetimeTimezoneTimestamp($threadDetails['createdAt']);
  1420.             if (!empty($threadDetails['attachments'])) {
  1421.                 $entityManager $this->entityManager;
  1422.                 $uvdeskFileSystemService $this->container->get('uvdesk.core.file_system.service');
  1423.                 $threadDetails['attachments'] = array_map(function ($attachment) use ($entityManager$uvdeskFileSystemService) {
  1424.                     $attachmentReferenceObject $this->entityManager->getReference(CoreFrameworkEntity\Attachment::class, $attachment['id']);
  1425.                     return $uvdeskFileSystemService->getFileTypeAssociations($attachmentReferenceObject);
  1426.                 }, $threadDetails['attachments']);
  1427.             }
  1428.         }
  1429.         return $threadDetails ?? null;
  1430.     }
  1431.     public function getSavedReplyContent($savedReplyId$ticketId)
  1432.     {
  1433.         $ticket $this->entityManager->getRepository(CoreFrameworkEntity\Ticket::class)->find($ticketId);
  1434.         $savedReply $this->entityManager->getRepository(CoreFrameworkEntity\SavedReplies::class)->findOneById($savedReplyId);
  1435.         $savedReplyGroups $savedReply->getSupportGroups()->toArray();
  1436.         $savedReplyTeams $savedReply->getSupportTeams()->toArray();
  1437.         $savedReplyGroups array_map(function ($group) {
  1438.             return $group->getId();
  1439.         }, $savedReplyGroups);
  1440.         $savedReplyTeams array_map(function ($team) {
  1441.             return $team->getId();
  1442.         }, $savedReplyTeams);
  1443.         $userInstance $this->container->get('user.service')->getCurrentUser()->getAgentInstance();
  1444.         $currentUserRole $userInstance->getSupportRole()->getCode();
  1445.         // Check if the user belongs to any of the groups or teams associated with the saved reply
  1446.         $userGroups $userInstance->getSupportGroups()->toArray();
  1447.         $userTeams $userInstance->getSupportTeams()->toArray();
  1448.         $groupIds array_map(function ($group) {
  1449.             return $group->getId();
  1450.         }, $userGroups);
  1451.         $teamIds array_map(function ($team) {
  1452.             return $team->getId();
  1453.         }, $userTeams);
  1454.         if (!array_intersect($groupIds$savedReplyGroups) && !array_intersect($teamIds$savedReplyTeams) && (!in_array($currentUserRole, ['ROLE_ADMIN''ROLE_SUPER_ADMIN']))) {
  1455.             throw new \Exception('You are not allowed to apply this saved reply.');
  1456.         }
  1457.         $emailPlaceholders $this->getSavedReplyPlaceholderValues($ticket'customer');
  1458.         return $this->container->get('email.service')->processEmailContent($savedReply->getMessage(), $emailPlaceholderstrue);
  1459.     }
  1460.     public function getSavedReplyPlaceholderValues($ticket$type "customer")
  1461.     {
  1462.         $variables = array();
  1463.         $variables['ticket.id'] = $ticket->getId();
  1464.         $variables['ticket.subject'] = $ticket->getSubject();
  1465.         $variables['ticket.status'] = $ticket->getStatus()->getCode();
  1466.         $variables['ticket.priority'] = $ticket->getPriority()->getCode();
  1467.         if ($ticket->getSupportGroup())
  1468.             $variables['ticket.group'] = $ticket->getSupportGroup()->getName();
  1469.         else
  1470.             $variables['ticket.group'] = '';
  1471.         $variables['ticket.team'] = ($ticket->getSupportTeam() ? $ticket->getSupportTeam()->getName() : '');
  1472.         $customer $this->container->get('user.service')->getCustomerPartialDetailById($ticket->getCustomer()->getId());
  1473.         $variables['ticket.customerName'] = $customer['name'];
  1474.         $variables['ticket.customerEmail'] = $customer['email'];
  1475.         $userService $this->container->get('user.service');
  1476.         $variables['ticket.agentName'] = '';
  1477.         $variables['ticket.agentEmail'] = '';
  1478.         if ($ticket->getAgent()) {
  1479.             $agent $this->container->get('user.service')->getAgentDetailById($ticket->getAgent()->getId());
  1480.             if ($agent) {
  1481.                 $variables['ticket.agentName'] = $agent['name'];
  1482.                 $variables['ticket.agentEmail'] = $agent['email'];
  1483.             }
  1484.         }
  1485.         $router $this->container->get('router');
  1486.         if ($type == 'customer') {
  1487.             $ticketListURL $router->generate('helpdesk_customer_ticket_collection', [
  1488.                 'id' => $ticket->getId(),
  1489.             ], UrlGeneratorInterface::ABSOLUTE_URL);
  1490.         } else {
  1491.             $ticketListURL $router->generate('helpdesk_member_ticket_collection', [
  1492.                 'id' => $ticket->getId(),
  1493.             ], UrlGeneratorInterface::ABSOLUTE_URL);
  1494.         }
  1495.         $variables['ticket.link'] = sprintf("<a href='%s'>#%s</a>"$ticketListURL$ticket->getId());
  1496.         return $variables;
  1497.     }
  1498.     public function isEmailBlocked($email$website)
  1499.     {
  1500.         $flag false;
  1501.         $email strtolower($email);
  1502.         $knowledgeBaseWebsite $this->entityManager->getRepository(KnowledgebaseWebsite::class)->findOneBy(['website' => $website->getId(), 'isActive' => 1]);
  1503.         $list $this->container->get('user.service')->getWebsiteSpamDetails($knowledgeBaseWebsite);
  1504.         // Blacklist
  1505.         if (!empty($list['blackList']['email']) && in_array($email$list['blackList']['email'])) {
  1506.             // Emails
  1507.             $flag true;
  1508.         } elseif (!empty($list['blackList']['domain'])) {
  1509.             // Domains
  1510.             foreach ($list['blackList']['domain'] as $domain) {
  1511.                 if (strpos($email$domain)) {
  1512.                     $flag true;
  1513.                     break;
  1514.                 }
  1515.             }
  1516.         }
  1517.         // Whitelist
  1518.         if ($flag) {
  1519.             if (isset($email$list['whiteList']['email']) && in_array($email$list['whiteList']['email'])) {
  1520.                 // Emails
  1521.                 return false;
  1522.             } elseif (isset($list['whiteList']['domain'])) {
  1523.                 // Domains
  1524.                 foreach ($list['whiteList']['domain'] as $domain) {
  1525.                     if (strpos($email$domain)) {
  1526.                         $flag false;
  1527.                     }
  1528.                 }
  1529.             }
  1530.         }
  1531.         return $flag;
  1532.     }
  1533.     public function timeZoneConverter($dateFlag)
  1534.     {
  1535.         $website $this->entityManager->getRepository(CoreFrameworkEntity\Website::class)->findOneBy(['code' => 'Knowledgebase']);
  1536.         $timeZone $website->getTimezone();
  1537.         $timeFormat $website->getTimeformat();
  1538.         $activeUser $this->container->get('user.service')->getSessionUser();
  1539.         $agentTimeZone = !empty($activeUser) ? $activeUser->getTimezone() : null;
  1540.         $agentTimeFormat = !empty($activeUser) ? $activeUser->getTimeformat() : null;
  1541.         $parameterType gettype($dateFlag);
  1542.         if ($parameterType == 'string') {
  1543.             if (is_null($agentTimeZone) && is_null($agentTimeFormat)) {
  1544.                 if (is_null($timeZone) && is_null($timeFormat)) {
  1545.                     $datePattern date_create($dateFlag);
  1546.                     return date_format($datePattern'd-m-Y h:ia');
  1547.                 } else {
  1548.                     $dateFlag = new \DateTime($dateFlag);
  1549.                     $datePattern $dateFlag->setTimezone(new \DateTimeZone($timeZone));
  1550.                     return date_format($datePattern$timeFormat);
  1551.                 }
  1552.             } else {
  1553.                 $dateFlag = new \DateTime($dateFlag);
  1554.                 $datePattern $dateFlag->setTimezone(new \DateTimeZone($agentTimeZone));
  1555.                 return date_format($datePattern$agentTimeFormat);
  1556.             }
  1557.         } else {
  1558.             if (is_null($agentTimeZone) && is_null($agentTimeFormat)) {
  1559.                 if (is_null($timeZone) && is_null($timeFormat)) {
  1560.                     return date_format($dateFlag'd-m-Y h:ia');
  1561.                 } else {
  1562.                     $datePattern $dateFlag->setTimezone(new \DateTimeZone($timeZone));
  1563.                     return date_format($datePattern$timeFormat);
  1564.                 }
  1565.             } else {
  1566.                 $datePattern $dateFlag->setTimezone(new \DateTimeZone($agentTimeZone));
  1567.                 return date_format($datePattern$agentTimeFormat);
  1568.             }
  1569.         }
  1570.     }
  1571.     public function fomatTimeByPreference($dbTime$timeZone$timeFormat$agentTimeZone$agentTimeFormat)
  1572.     {
  1573.         if (is_null($agentTimeZone) && is_null($agentTimeFormat)) {
  1574.             if (is_null($timeZone) && is_null($timeFormat)) {
  1575.                 $dateTimeZone $dbTime;
  1576.                 $timeFormatString 'd-m-Y h:ia';
  1577.             } else {
  1578.                 $dateTimeZone $dbTime->setTimezone(new \DateTimeZone($timeZone));
  1579.                 $timeFormatString $timeFormat;
  1580.             }
  1581.         } else {
  1582.             $dateTimeZone $dbTime->setTimezone(new \DateTimeZone($agentTimeZone));
  1583.             $timeFormatString $agentTimeFormat;
  1584.         }
  1585.         $time['dateTimeZone'] = $dateTimeZone;
  1586.         $time['timeFormatString'] = $timeFormatString;
  1587.         return $time;
  1588.     }
  1589.     public function isTicketAccessGranted(CoreFrameworkEntity\Ticket $ticketCoreFrameworkEntity\User $user null$firewall 'members')
  1590.     {
  1591.         // @TODO: Take current firewall into consideration (access check on behalf of agent/customer)
  1592.         if (empty($user)) {
  1593.             $user $this->container->get('user.service')->getSessionUser();
  1594.         }
  1595.         if (empty($user)) {
  1596.             return false;
  1597.         } else {
  1598.             $agentInstance $user->getAgentInstance();
  1599.             if (empty($agentInstance)) {
  1600.                 return false;
  1601.             }
  1602.         }
  1603.         if ($agentInstance->getSupportRole()->getId() == && in_array($agentInstance->getTicketAccessLevel(), [234])) {
  1604.             $accessLevel $agentInstance->getTicketAccessLevel();
  1605.             // Check if user has been given inidividual access
  1606.             if ($ticket->getAgent() != null && $ticket->getAgent()->getId() == $user->getId()) {
  1607.                 return true;
  1608.             }
  1609.             if ($accessLevel == || $accessLevel == 3) {
  1610.                 // Check if user belongs to a support team assigned to ticket
  1611.                 $teamReferenceIds array_map(function ($team) {
  1612.                     return $team->getId();
  1613.                 }, $agentInstance->getSupportTeams()->toArray());
  1614.                 if ($ticket->getSupportTeam() != null && in_array($ticket->getSupportTeam()->getId(), $teamReferenceIds)) {
  1615.                     return true;
  1616.                 } else if ($accessLevel == 2) {
  1617.                     // Check if user belongs to a support group assigned to ticket
  1618.                     $groupReferenceIds array_map(function ($group) {
  1619.                         return $group->getId();
  1620.                     }, $agentInstance->getSupportGroups()->toArray());
  1621.                     if ($ticket->getSupportGroup() != null && in_array($ticket->getSupportGroup()->getId(), $groupReferenceIds)) {
  1622.                         return true;
  1623.                     }
  1624.                 }
  1625.             }
  1626.             return false;
  1627.         }
  1628.         return true;
  1629.     }
  1630.     public function addTicketCustomFields($thread$submittedCustomFields = [], $uploadedFilesCollection = [])
  1631.     {
  1632.         $customFieldsService null;
  1633.         $customFieldsEntityReference null;
  1634.         if ($this->userService->isFileExists('apps/uvdesk/custom-fields')) {
  1635.             $customFieldsService $this->container->get('uvdesk_package_custom_fields.service');
  1636.             $customFieldsEntityReference UVDeskCommunityPackages\CustomFields\Entity\CustomFields::class;
  1637.             $customFieldValuesEntityReference UVDeskCommunityPackages\CustomFields\Entity\CustomFieldsValues::class;
  1638.             $ticketCustomFieldValuesEntityReference UVDeskCommunityPackages\CustomFields\Entity\TicketCustomFieldsValues::class;
  1639.         } else if ($this->userService->isFileExists('apps/uvdesk/form-component')) {
  1640.             $customFieldsService $this->container->get('uvdesk_package_form_component.service');
  1641.             $customFieldsEntityReference UVDeskCommunityPackages\FormComponent\Entity\CustomFields::class;
  1642.             $customFieldValuesEntityReference UVDeskCommunityPackages\FormComponent\Entity\CustomFieldsValues::class;
  1643.             $ticketCustomFieldValuesEntityReference UVDeskCommunityPackages\FormComponent\Entity\TicketCustomFieldsValues::class;
  1644.         } else {
  1645.             return;
  1646.         }
  1647.         $ticket $thread->getTicket();
  1648.         $customFieldsCollection $this->entityManager->getRepository($customFieldsEntityReference)->findAll();
  1649.         $customFieldValuesEntityRepository $this->entityManager->getRepository($customFieldValuesEntityReference);
  1650.         foreach ($customFieldsCollection as $customFields) {
  1651.             if (in_array($customFields->getFieldType(), ['select''checkbox''radio']) && !count($customFields->getCustomFieldValues())) {
  1652.                 continue;
  1653.             }
  1654.             if (
  1655.                 !empty($submittedCustomFields)
  1656.                 && $customFields->getFieldType() != 'file'
  1657.                 && isset($submittedCustomFields[$customFields->getId()])
  1658.             ) {
  1659.                 // Check if custom field dependency criterias are fullfilled
  1660.                 if (
  1661.                     count($customFields->getCustomFieldsDependency())
  1662.                     && !in_array($ticket->getType(), $customFields->getCustomFieldsDependency()->toArray())
  1663.                 ) {
  1664.                     continue;
  1665.                 }
  1666.                 // Save ticket custom fields
  1667.                 $ticketCustomField = new $ticketCustomFieldValuesEntityReference();
  1668.                 $ticketCustomField
  1669.                     ->setTicket($ticket)
  1670.                     ->setTicketCustomFieldsValues($customFields)
  1671.                     ->setValue(json_encode($submittedCustomFields[$customFields->getId()]))
  1672.                 ;
  1673.                 if (in_array($customFields->getFieldType(), ['select''checkbox''radio'])) {
  1674.                     // Add custom field values mapping too
  1675.                     if (is_array($submittedCustomFields[$customFields->getId()])) {
  1676.                         foreach ($submittedCustomFields[$customFields->getId()] as $value) {
  1677.                             $ticketCustomFieldValues $customFieldValuesEntityRepository->findOneBy([
  1678.                                 'id'           => $value,
  1679.                                 'customFields' => $customFields,
  1680.                             ]);
  1681.                             if (!empty($ticketCustomFieldValues)) {
  1682.                                 $ticketCustomField
  1683.                                     ->setTicketCustomFieldValueValues($ticketCustomFieldValues);
  1684.                             }
  1685.                         }
  1686.                     } else {
  1687.                         $ticketCustomFieldValues $customFieldValuesEntityRepository->findOneBy([
  1688.                             'id'           => $submittedCustomFields[$customFields->getId()],
  1689.                             'customFields' => $customFields,
  1690.                         ]);
  1691.                         if (!empty($ticketCustomFieldValues)) {
  1692.                             $ticketCustomField
  1693.                                 ->setTicketCustomFieldValueValues($ticketCustomFieldValues);
  1694.                         }
  1695.                     }
  1696.                 }
  1697.                 $this->entityManager->persist($ticketCustomField);
  1698.                 $this->entityManager->flush();
  1699.             } else if (
  1700.                 !empty($uploadedFilesCollection)
  1701.                 && isset($uploadedFilesCollection[$customFields->getId()])
  1702.             ) {
  1703.                 // Upload files
  1704.                 $path '/custom-fields/ticket/' $ticket->getId() . '/';
  1705.                 $fileNames $this->fileUploadService->uploadFile($uploadedFilesCollection[$customFields->getid()], $pathtrue);
  1706.                 if (!empty($fileNames)) {
  1707.                     // Save files entry to attachment table
  1708.                     try {
  1709.                         $newFilesNames $customFieldsService->addFilesEntryToAttachmentTable([$fileNames], $thread);
  1710.                         foreach ($newFilesNames as $value) {
  1711.                             // Save ticket custom fields
  1712.                             $ticketCustomField = new $ticketCustomFieldValuesEntityReference();
  1713.                             $ticketCustomField
  1714.                                 ->setTicket($ticket)
  1715.                                 ->setTicketCustomFieldsValues($customFields)
  1716.                                 ->setValue(json_encode([
  1717.                                     'name' => $value['name'],
  1718.                                     'path' => $value['path'],
  1719.                                     'id'   => $value['id'],
  1720.                                 ]))
  1721.                             ;
  1722.                             $this->entityManager->persist($ticketCustomField);
  1723.                             $this->entityManager->flush();
  1724.                         }
  1725.                     } catch (\Exception $e) {
  1726.                         // @TODO: Log execption message
  1727.                     }
  1728.                 }
  1729.             }
  1730.         }
  1731.     }
  1732.     // return attachemnt for initial thread
  1733.     public function getInitialThread($ticketId)
  1734.     {
  1735.         $firstThread null;
  1736.         $intialThread $this->entityManager->getRepository(CoreFrameworkEntity\Thread::class)->findBy(['ticket' => $ticketId]);
  1737.         foreach ($intialThread as $key => $value) {
  1738.             if ($value->getThreadType() == "create") {
  1739.                 $firstThread $value;
  1740.             }
  1741.         }
  1742.         return $firstThread;
  1743.     }
  1744.     public function getTicketConditions()
  1745.     {
  1746.         $conditions = array(
  1747.             'ticket' => [
  1748.                 ('mail') => array(
  1749.                     [
  1750.                         'lable' => ('from_mail'),
  1751.                         'value' => 'from_mail',
  1752.                         'match' => 'email'
  1753.                     ],
  1754.                     [
  1755.                         'lable' => ('to_mail'),
  1756.                         'value' => 'to_mail',
  1757.                         'match' => 'email'
  1758.                     ],
  1759.                 ),
  1760.                 ('API') => array(
  1761.                     [
  1762.                         'lable' => ('Domain'),
  1763.                         'value' => 'domain',
  1764.                         'match' => 'api'
  1765.                     ],
  1766.                     [
  1767.                         'lable' => ('Locale'),
  1768.                         'value' => 'locale',
  1769.                         'match' => 'api'
  1770.                     ],
  1771.                 ),
  1772.                 ('ticket') => array(
  1773.                     [
  1774.                         'lable' => ('subject'),
  1775.                         'value' => 'subject',
  1776.                         'match' => 'string'
  1777.                     ],
  1778.                     [
  1779.                         'lable' => ('description'),
  1780.                         'value' => 'description',
  1781.                         'match' => 'string'
  1782.                     ],
  1783.                     [
  1784.                         'lable' => ('subject_or_description'),
  1785.                         'value' => 'subject_or_description',
  1786.                         'match' => 'string'
  1787.                     ],
  1788.                     [
  1789.                         'lable' => ('priority'),
  1790.                         'value' => 'priority',
  1791.                         'match' => 'select'
  1792.                     ],
  1793.                     [
  1794.                         'lable' => ('type'),
  1795.                         'value' => 'type',
  1796.                         'match' => 'select'
  1797.                     ],
  1798.                     [
  1799.                         'lable' => ('status'),
  1800.                         'value' => 'status',
  1801.                         'match' => 'select'
  1802.                     ],
  1803.                     [
  1804.                         'lable' => ('source'),
  1805.                         'value' => 'source',
  1806.                         'match' => 'select'
  1807.                     ],
  1808.                     [
  1809.                         'lable' => ('created'),
  1810.                         'value' => 'created',
  1811.                         'match' => 'date'
  1812.                     ],
  1813.                     [
  1814.                         'lable' => ('agent'),
  1815.                         'value' => 'agent',
  1816.                         'match' => 'select'
  1817.                     ],
  1818.                     [
  1819.                         'lable' => ('group'),
  1820.                         'value' => 'group',
  1821.                         'match' => 'select'
  1822.                     ],
  1823.                     [
  1824.                         'lable' => ('team'),
  1825.                         'value' => 'team',
  1826.                         'match' => 'select'
  1827.                     ],
  1828.                 ),
  1829.                 ('customer') => array(
  1830.                     [
  1831.                         'lable' => ('customer_name'),
  1832.                         'value' => 'customer_name',
  1833.                         'match' => 'string'
  1834.                     ],
  1835.                     [
  1836.                         'lable' => ('customer_email'),
  1837.                         'value' => 'customer_email',
  1838.                         'match' => 'email'
  1839.                     ],
  1840.                 ),
  1841.             ],
  1842.             'task' => [
  1843.                 ('task') => array(
  1844.                     [
  1845.                         'lable' => ('subject'),
  1846.                         'value' => 'subject',
  1847.                         'match' => 'string'
  1848.                     ],
  1849.                     [
  1850.                         'lable' => ('description'),
  1851.                         'value' => 'description',
  1852.                         'match' => 'string'
  1853.                     ],
  1854.                     [
  1855.                         'lable' => ('subject_or_description'),
  1856.                         'value' => 'subject_or_description',
  1857.                         'match' => 'string'
  1858.                     ],
  1859.                     [
  1860.                         'lable' => ('priority'),
  1861.                         'value' => 'priority',
  1862.                         'match' => 'select'
  1863.                     ],
  1864.                     [
  1865.                         'lable' => ('stage'),
  1866.                         'value' => 'stage',
  1867.                         'match' => 'select'
  1868.                     ],
  1869.                     [
  1870.                         'lable' => ('created'),
  1871.                         'value' => 'created',
  1872.                         'match' => 'date'
  1873.                     ],
  1874.                     [
  1875.                         'lable' => ('agent_name'),
  1876.                         'value' => 'agent_name',
  1877.                         'match' => 'select'
  1878.                     ],
  1879.                     [
  1880.                         'lable' => ('agent_email'),
  1881.                         'value' => 'agent_email',
  1882.                         'match' => 'select'
  1883.                     ],
  1884.                 ),
  1885.             ]
  1886.         );
  1887.         return $conditions;
  1888.     }
  1889.     public function getAgentMatchConditions()
  1890.     {
  1891.         return [
  1892.             'email' => array(
  1893.                 [
  1894.                     'lable' => ('is'),
  1895.                     'value' => 'is'
  1896.                 ],
  1897.                 [
  1898.                     'lable' => ('isNot'),
  1899.                     'value' => 'isNot'
  1900.                 ],
  1901.                 [
  1902.                     'lable' => ('contains'),
  1903.                     'value' => 'contains'
  1904.                 ],
  1905.                 [
  1906.                     'lable' => ('notContains'),
  1907.                     'value' => 'notContains'
  1908.                 ],
  1909.             ),
  1910.             'api' => array(
  1911.                 [
  1912.                     'lable' => ('is'),
  1913.                     'value' => 'is'
  1914.                 ],
  1915.                 [
  1916.                     'lable' => ('contains'),
  1917.                     'value' => 'contains'
  1918.                 ],
  1919.             ),
  1920.             'string' => array(
  1921.                 [
  1922.                     'lable' => ('is'),
  1923.                     'value' => 'is'
  1924.                 ],
  1925.                 [
  1926.                     'lable' => ('isNot'),
  1927.                     'value' => 'isNot'
  1928.                 ],
  1929.                 [
  1930.                     'lable' => ('contains'),
  1931.                     'value' => 'contains'
  1932.                 ],
  1933.                 [
  1934.                     'lable' => ('notContains'),
  1935.                     'value' => 'notContains'
  1936.                 ],
  1937.                 [
  1938.                     'lable' => ('startWith'),
  1939.                     'value' => 'startWith'
  1940.                 ],
  1941.                 [
  1942.                     'lable' => ('endWith'),
  1943.                     'value' => 'endWith'
  1944.                 ],
  1945.             ),
  1946.             'select' => array(
  1947.                 [
  1948.                     'lable' => ('is'),
  1949.                     'value' => 'is'
  1950.                 ],
  1951.             ),
  1952.             'date' => array(
  1953.                 [
  1954.                     'lable' => ('before'),
  1955.                     'value' => 'before'
  1956.                 ],
  1957.                 [
  1958.                     'lable' => ('beforeOn'),
  1959.                     'value' => 'beforeOn'
  1960.                 ],
  1961.                 [
  1962.                     'lable' => ('after'),
  1963.                     'value' => 'after'
  1964.                 ],
  1965.                 [
  1966.                     'lable' => ('afterOn'),
  1967.                     'value' => 'afterOn'
  1968.                 ],
  1969.             ),
  1970.             'datetime' => array(
  1971.                 [
  1972.                     'lable' => ('before'),
  1973.                     'value' => 'beforeDateTime'
  1974.                 ],
  1975.                 [
  1976.                     'lable' => ('beforeOn'),
  1977.                     'value' => 'beforeDateTimeOn'
  1978.                 ],
  1979.                 [
  1980.                     'lable' => ('after'),
  1981.                     'value' => 'afterDateTime'
  1982.                 ],
  1983.                 [
  1984.                     'lable' => ('afterOn'),
  1985.                     'value' => 'afterDateTimeOn'
  1986.                 ],
  1987.             ),
  1988.             'time' => array(
  1989.                 [
  1990.                     'lable' => ('before'),
  1991.                     'value' => 'beforeTime'
  1992.                 ],
  1993.                 [
  1994.                     'lable' => ('beforeOn'),
  1995.                     'value' => 'beforeTimeOn'
  1996.                 ],
  1997.                 [
  1998.                     'lable' => ('after'),
  1999.                     'value' => 'afterTime'
  2000.                 ],
  2001.                 [
  2002.                     'lable' => ('afterOn'),
  2003.                     'value' => 'afterTimeOn'
  2004.                 ],
  2005.             ),
  2006.             'number' => array(
  2007.                 [
  2008.                     'lable' => ('is'),
  2009.                     'value' => 'is'
  2010.                 ],
  2011.                 [
  2012.                     'lable' => ('isNot'),
  2013.                     'value' => 'isNot'
  2014.                 ],
  2015.                 [
  2016.                     'lable' => ('contains'),
  2017.                     'value' => 'contains'
  2018.                 ],
  2019.                 [
  2020.                     'lable' => ('greaterThan'),
  2021.                     'value' => 'greaterThan'
  2022.                 ],
  2023.                 [
  2024.                     'lable' => ('lessThan'),
  2025.                     'value' => 'lessThan'
  2026.                 ],
  2027.             ),
  2028.         ];
  2029.     }
  2030.     public function getTicketMatchConditions()
  2031.     {
  2032.         return [
  2033.             'email' => array(
  2034.                 [
  2035.                     'lable' => ('is'),
  2036.                     'value' => 'is'
  2037.                 ],
  2038.                 [
  2039.                     'lable' => ('isNot'),
  2040.                     'value' => 'isNot'
  2041.                 ],
  2042.                 [
  2043.                     'lable' => ('contains'),
  2044.                     'value' => 'contains'
  2045.                 ],
  2046.                 [
  2047.                     'lable' => ('notContains'),
  2048.                     'value' => 'notContains'
  2049.                 ],
  2050.             ),
  2051.             'api' => array(
  2052.                 [
  2053.                     'lable' => ('is'),
  2054.                     'value' => 'is'
  2055.                 ],
  2056.                 [
  2057.                     'lable' => ('contains'),
  2058.                     'value' => 'contains'
  2059.                 ],
  2060.             ),
  2061.             'string' => array(
  2062.                 [
  2063.                     'lable' => ('is'),
  2064.                     'value' => 'is'
  2065.                 ],
  2066.                 [
  2067.                     'lable' => ('isNot'),
  2068.                     'value' => 'isNot'
  2069.                 ],
  2070.                 [
  2071.                     'lable' => ('contains'),
  2072.                     'value' => 'contains'
  2073.                 ],
  2074.                 [
  2075.                     'lable' => ('notContains'),
  2076.                     'value' => 'notContains'
  2077.                 ],
  2078.                 [
  2079.                     'lable' => ('startWith'),
  2080.                     'value' => 'startWith'
  2081.                 ],
  2082.                 [
  2083.                     'lable' => ('endWith'),
  2084.                     'value' => 'endWith'
  2085.                 ],
  2086.             ),
  2087.             'select' => array(
  2088.                 [
  2089.                     'lable' => ('is'),
  2090.                     'value' => 'is'
  2091.                 ],
  2092.                 [
  2093.                     'lable' => ('isNot'),
  2094.                     'value' => 'isNot'
  2095.                 ],
  2096.             ),
  2097.             'date' => array(
  2098.                 [
  2099.                     'lable' => ('before'),
  2100.                     'value' => 'before'
  2101.                 ],
  2102.                 [
  2103.                     'lable' => ('beforeOn'),
  2104.                     'value' => 'beforeOn'
  2105.                 ],
  2106.                 [
  2107.                     'lable' => ('after'),
  2108.                     'value' => 'after'
  2109.                 ],
  2110.                 [
  2111.                     'lable' => ('afterOn'),
  2112.                     'value' => 'afterOn'
  2113.                 ],
  2114.             ),
  2115.             'datetime' => array(
  2116.                 [
  2117.                     'lable' => ('before'),
  2118.                     'value' => 'beforeDateTime'
  2119.                 ],
  2120.                 [
  2121.                     'lable' => ('beforeOn'),
  2122.                     'value' => 'beforeDateTimeOn'
  2123.                 ],
  2124.                 [
  2125.                     'lable' => ('after'),
  2126.                     'value' => 'afterDateTime'
  2127.                 ],
  2128.                 [
  2129.                     'lable' => ('afterOn'),
  2130.                     'value' => 'afterDateTimeOn'
  2131.                 ],
  2132.             ),
  2133.             'time' => array(
  2134.                 [
  2135.                     'lable' => ('before'),
  2136.                     'value' => 'beforeTime'
  2137.                 ],
  2138.                 [
  2139.                     'lable' => ('beforeOn'),
  2140.                     'value' => 'beforeTimeOn'
  2141.                 ],
  2142.                 [
  2143.                     'lable' => ('after'),
  2144.                     'value' => 'afterTime'
  2145.                 ],
  2146.                 [
  2147.                     'lable' => ('afterOn'),
  2148.                     'value' => 'afterTimeOn'
  2149.                 ],
  2150.             ),
  2151.             'number' => array(
  2152.                 [
  2153.                     'lable' => ('is'),
  2154.                     'value' => 'is'
  2155.                 ],
  2156.                 [
  2157.                     'lable' => ('isNot'),
  2158.                     'value' => 'isNot'
  2159.                 ],
  2160.                 [
  2161.                     'lable' => ('contains'),
  2162.                     'value' => 'contains'
  2163.                 ],
  2164.                 [
  2165.                     'lable' => ('greaterThan'),
  2166.                     'value' => 'greaterThan'
  2167.                 ],
  2168.                 [
  2169.                     'lable' => ('lessThan'),
  2170.                     'value' => 'lessThan'
  2171.                 ],
  2172.             ),
  2173.         ];
  2174.     }
  2175.     public function getTargetAction()
  2176.     {
  2177.         return [
  2178.             '4' => ['response' => ['time' => '2''unit' => 'hours'], 'resolve' => ['time' => '8''unit' => 'hours'], 'operational' => 'calendarHours''isActive' => 'on'],
  2179.             '3' => ['response' => ['time' => '4''unit' => 'hours'], 'resolve' => ['time' => '1''unit' => 'days'], 'operational' => 'calendarHours''isActive' => 'on'],
  2180.             '2' => ['response' => ['time' => '8''unit' => 'hours'], 'resolve' => ['time' => '3''unit' => 'days'], 'operational' => 'calendarHours''isActive' => 'on'],
  2181.             '1' => ['response' => ['time' => '16''unit' => 'hours'], 'resolve' => ['time' => '5''unit' => 'days'], 'operational' => 'calendarHours''isActive' => 'on'],
  2182.         ];
  2183.     }
  2184.     public function getTicketActions($force false)
  2185.     {
  2186.         $actionArray =  array(
  2187.             'ticket' => [
  2188.                 'priority'               => ('action.priority'),
  2189.                 'type'                   => ('action.type'),
  2190.                 'status'                 => ('action.status'),
  2191.                 'tag'                    => ('action.tag'),
  2192.                 'note'                   => ('action.note'),
  2193.                 'label'                  => ('action.label'),
  2194.                 'assign_agent'           => ('action.assign_agent'),
  2195.                 'assign_group'           => ('action.assign_group'),
  2196.                 'assign_team'            => ('action.assign_team'),
  2197.                 'mail_agent'             => ('action.mail_agent'),
  2198.                 'mail_group'             => ('action.mail_group'),
  2199.                 'mail_team'              => ('action.mail_team'),
  2200.                 'mail_customer'          => ('action.mail_customer'),
  2201.                 'mail_last_collaborator' => ('action.mail_last_collaborator'),
  2202.                 'mail_all_collaborators' => ('action.mail_all_collaborators'),
  2203.                 'delete_ticket'          => ('action.delete_ticket'),
  2204.                 'mark_spam'              => ('action.mark_spam'),
  2205.             ],
  2206.             'task' => [
  2207.                 'reply'            => ('action.reply'),
  2208.                 'mail_agent'       => ('action.mail_agent'),
  2209.                 'mail_members'     => ('action.mail_members'),
  2210.                 'mail_last_member' => ('action.mail_last_member'),
  2211.             ],
  2212.             'customer' => [
  2213.                 'mail_customer' => ('action.mail_customer'),
  2214.             ],
  2215.             'agent' => [
  2216.                 'mail_agent'    => ('action.mail_agent'),
  2217.                 'task_transfer' => ('action.task_transfer'),
  2218.                 'assign_agent'  => ('action.assign_agent'),
  2219.                 'assign_group'  => ('action.assign_group'),
  2220.                 'assign_team'   => ('action.assign_team'),
  2221.             ],
  2222.         );
  2223.         $actionRoleArray = [
  2224.             'ticket->priority'               => 'ROLE_AGENT_UPDATE_TICKET_PRIORITY',
  2225.             'ticket->type'                   => 'ROLE_AGENT_UPDATE_TICKET_TYPE',
  2226.             'ticket->status'                 => 'ROLE_AGENT_UPDATE_TICKET_STATUS',
  2227.             'ticket->tag'                    => 'ROLE_AGENT_ADD_TAG',
  2228.             'ticket->note'                   => 'ROLE_AGENT_ADD_NOTE',
  2229.             'ticket->assign_agent'           => 'ROLE_AGENT_ASSIGN_TICKET',
  2230.             'ticket->assign_group'           => 'ROLE_AGENT_ASSIGN_TICKET_GROUP',
  2231.             'ticket->assign_team'            => 'ROLE_AGENT_ASSIGN_TICKET_GROUP',
  2232.             'ticket->mail_agent'             => 'ROLE_AGENT',
  2233.             'ticket->mail_group'             => 'ROLE_AGENT_MANAGE_GROUP',
  2234.             'ticket->mail_team'              => 'ROLE_AGENT_MANAGE_SUB_GROUP',
  2235.             'ticket->mail_customer'          => 'ROLE_AGENT',
  2236.             'ticket->mail_last_collaborator' => 'ROLE_AGENT',
  2237.             'ticket->mail_all_collaborators' => 'ROLE_AGENT',
  2238.             'ticket->delete_ticket'          => 'ROLE_AGENT_DELETE_TICKET',
  2239.             'ticket->mark_spam'              => 'ROLE_AGENT_UPDATE_TICKET_STATUS',
  2240.             'ticket->label'                  => 'ROLE_ADMIN',
  2241.             'task->reply'                    => 'ROLE_AGENT',
  2242.             'task->mail_agent'               => 'ROLE_AGENT',
  2243.             'task->mail_members'             => 'ROLE_AGENT',
  2244.             'task->mail_last_member'         => 'ROLE_AGENT',
  2245.             'customer->mail_customer'        => 'ROLE_AGENT',
  2246.             'agent->mail_agent'              => 'ROLE_AGENT',
  2247.             'agent->task_transfer'           => 'ROLE_AGENT_EDIT_TASK',
  2248.             'agent->assign_agent'            => 'ROLE_AGENT_ASSIGN_TICKET',
  2249.             'agent->assign_group'            => 'ROLE_AGENT_ASSIGN_TICKET_GROUP',
  2250.             'agent->assign_team'             => 'ROLE_AGENT_ASSIGN_TICKET_GROUP',
  2251.         ];
  2252.         $resultArray = [];
  2253.         foreach ($actionRoleArray as $action => $role) {
  2254.             if ($role == 'ROLE_AGENT' || $this->container->get('user.service')->checkPermission($role) || $force) {
  2255.                 $actionPath explode('->'$action);
  2256.                 $resultArray[$actionPath[0]][$actionPath[1]] = $actionArray[$actionPath[0]][$actionPath[1]];
  2257.             }
  2258.         }
  2259.         $repo $this->container->get('doctrine.orm.entity_manager')->getRepository('WebkulAppBundle:ECommerceChannel');
  2260.         $ecomArray = [];
  2261.         $ecomChannels $repo->getActiveChannelsByCompany($this->container->get('user.service')->getCurrentCompany());
  2262.         foreach ($ecomChannels as $channel) {
  2263.             $ecomArray['add_order_to_' $channel['id']] = ('Add order to: ') . $channel['title'];
  2264.         }
  2265.         $resultArray['ticket'] = array_merge($resultArray['ticket'], $ecomArray);
  2266.         return $resultArray;
  2267.     }
  2268.     /**
  2269.      * Generates a unique url that can be used to access ticket without any active session with read only priviliges.
  2270.      */
  2271.     public function generateTicketCustomerReadOnlyResourceAccessLink(CoreFrameworkEntity\Ticket $ticket)
  2272.     {
  2273.         $customer $ticket->getCustomer();
  2274.         $router $this->container->get('router');
  2275.         $token $this->generateCustomToken($ticket->getId());
  2276.         $publicResourceAccessLink = new CoreFrameworkEntity\PublicResourceAccessLink();
  2277.         $publicResourceAccessLink
  2278.             ->setResourceId($ticket->getId())
  2279.             ->setResourceType(CoreFrameworkEntity\Ticket::class)
  2280.             ->setUniqueResourceAccessId($token)
  2281.             ->setTotalViews(0)
  2282.             ->setCreatedAt(new \DateTime('now'))
  2283.             ->setExpiresAt(new \DateTime('+1 week'))
  2284.             ->setIsExpired(false)
  2285.             ->setUser($customer)
  2286.         ;
  2287.         $this->entityManager->persist($publicResourceAccessLink);
  2288.         $this->entityManager->flush();
  2289.         return $router->generate('helpdesk_customer_public_resource_access_intermediate', [
  2290.             'urid' => $publicResourceAccessLink->getUniqueResourceAccessId(),
  2291.         ], UrlGeneratorInterface::ABSOLUTE_URL);
  2292.     }
  2293.     // Generate a token based on ticket, timestamp and random string.
  2294.     public function generateCustomToken($ticketId$randomLength 6)
  2295.     {
  2296.         // Convert the current timestamp to Base36
  2297.         $timestamp base_convert(time(), 1036);
  2298.         // Add the ticket ID
  2299.         $ticketPart strtoupper(base_convert($ticketId1036)); // Convert ticket ID to Base36 (uppercase for readability)
  2300.         // Generate a random string of specified length
  2301.         $characters '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
  2302.         $randomString '';
  2303.         for ($i 0$i $randomLength$i++) {
  2304.             $randomString .= $characters[random_int(0strlen($characters) - 1)];
  2305.         }
  2306.         // Combine all parts: timestamp + ticket ID + random string
  2307.         return $timestamp $ticketPart $randomString;
  2308.     }
  2309.     public function sanitizeMessage($html)
  2310.     {
  2311.         // Step 1: Normalize the input by decoding multiple levels of encoding
  2312.         $originalHtml $html;
  2313.         $iterations 0;
  2314.         $maxIterations 5// Prevent infinite loops
  2315.         do {
  2316.             $previousHtml $html;
  2317.             // Decode HTML entities (handles &lt; &gt; &amp; &quot; &#39; etc.)
  2318.             $html html_entity_decode($htmlENT_QUOTES ENT_HTML5'UTF-8');
  2319.             // Also decode numeric entities like &#60; &#62;
  2320.             $html preg_replace_callback('/&#(\d+);/', function ($matches) {
  2321.                 return chr($matches[1]);
  2322.             }, $html);
  2323.             // Decode hex entities like &#x3C; &#x3E;
  2324.             $html preg_replace_callback('/&#x([0-9a-fA-F]+);/', function ($matches) {
  2325.                 return chr(hexdec($matches[1]));
  2326.             }, $html);
  2327.             $iterations++;
  2328.         } while ($html !== $previousHtml && $iterations $maxIterations);
  2329.         // Step 2: Remove all script tags and their content (multiple variations)
  2330.         $scriptPatterns = [
  2331.             '/<\s*script[^>]*>.*?<\s*\/\s*script\s*>/is',
  2332.             '/<\s*script[^>]*>.*?$/is'// Unclosed script tags
  2333.             '/javascript\s*:/i',
  2334.             '/vbscript\s*:/i',
  2335.             '/data\s*:\s*text\/html/i',
  2336.             '/data\s*:\s*application\/javascript/i'
  2337.         ];
  2338.         foreach ($scriptPatterns as $pattern) {
  2339.             $html preg_replace($pattern''$html);
  2340.         }
  2341.         // Step 3: Remove dangerous tags
  2342.         $dangerousTags = [
  2343.             'script',
  2344.             'iframe',
  2345.             'object',
  2346.             'embed',
  2347.             'form',
  2348.             'input',
  2349.             'textarea',
  2350.             'button',
  2351.             'select',
  2352.             'option',
  2353.             'style',
  2354.             'link',
  2355.             'meta',
  2356.             'base',
  2357.             'applet',
  2358.             'bgsound',
  2359.             'blink',
  2360.             'body',
  2361.             'frame',
  2362.             'frameset',
  2363.             'head',
  2364.             'html',
  2365.             'ilayer',
  2366.             'layer',
  2367.             'plaintext',
  2368.             'title',
  2369.             'xml',
  2370.             'xmp'
  2371.         ];
  2372.         foreach ($dangerousTags as $tag) {
  2373.             // Remove opening and closing tags
  2374.             $html preg_replace('/<\s*\/?\s*' preg_quote($tag'/') . '[^>]*>/is'''$html);
  2375.         }
  2376.         // Step 4: Remove ALL event handlers and dangerous attributes
  2377.         $dangerousAttributes = [
  2378.             // Event handlers
  2379.             'on\w+'// Catches onclick, onload, onerror, etc.
  2380.             // Other dangerous attributes
  2381.             'style',
  2382.             'background',
  2383.             'dynsrc',
  2384.             'lowsrc',
  2385.             'href\s*=\s*["\']?\s*javascript',
  2386.             'src\s*=\s*["\']?\s*javascript',
  2387.             'action',
  2388.             'formaction',
  2389.             'poster',
  2390.             'cite',
  2391.             'longdesc',
  2392.             'profile',
  2393.             'usemap'
  2394.         ];
  2395.         foreach ($dangerousAttributes as $attr) {
  2396.             $html preg_replace('/\s+' $attr '\s*=\s*(["\'])[^"\']*\1/i'''$html);
  2397.             $html preg_replace('/\s+' $attr '\s*=\s*[^\s>]+/i'''$html);
  2398.         }
  2399.         // Step 5: Remove dangerous protocols from remaining attributes
  2400.         $html preg_replace('/(href|src|action|formaction|background|cite|longdesc|profile|usemap)\s*=\s*(["\']?)(javascript|data|vbscript|about|chrome|file|ftp|jar|mailto|ms-its|mhtml|mocha|opera|res|resource|shell|view-source|wyciwyg):[^"\'>\s]*/i''$1=$2#blocked'$html);
  2401.         // Step 6: Strip tags - only allow explicitly safe ones
  2402.         $allowedTags '<p><b><i><strong><em><ul><ol><li><br><a><img><h1><h2><h3><h4><h5><h6><blockquote><code><pre><span><div>';
  2403.         $html strip_tags($html$allowedTags);
  2404.         // Step 7: Final validation and cleanup of remaining tags
  2405.         $html preg_replace_callback('/<(\w+)([^>]*)>/i', function ($matches) {
  2406.             $tag strtolower($matches[1]);
  2407.             $attributes $matches[2];
  2408.             // Only allow specific attributes for specific tags
  2409.             $allowedAttributes = [
  2410.                 'a'    => ['href''title''target'],
  2411.                 'img'  => ['src''alt''width''height''title'],
  2412.                 'p'    => ['class'],
  2413.                 'div'  => ['class'],
  2414.                 'span' => ['class']
  2415.             ];
  2416.             if (!isset($allowedAttributes[$tag])) {
  2417.                 // Tag has no allowed attributes, return just the tag
  2418.                 return '<' $tag '>';
  2419.             }
  2420.             $validAttrs = [];
  2421.             foreach ($allowedAttributes[$tag] as $allowedAttr) {
  2422.                 if (preg_match('/\s+' preg_quote($allowedAttr'/') . '\s*=\s*(["\'])([^"\']*)\1/i'$attributes$match)) {
  2423.                     $value $match[2];
  2424.                     // Additional validation for specific attributes
  2425.                     if ($allowedAttr === 'href') {
  2426.                         // Only allow safe URLs
  2427.                         if (preg_match('/^(https?:\/\/|mailto:|\/|#)/i'$value)) {
  2428.                             $validAttrs[] = $allowedAttr '="' htmlspecialchars($valueENT_QUOTES'UTF-8') . '"';
  2429.                         }
  2430.                     } elseif ($allowedAttr === 'src') {
  2431.                         // Only allow safe image sources
  2432.                         if (preg_match('/^(https?:\/\/|\/)/i'$value)) {
  2433.                             $validAttrs[] = $allowedAttr '="' htmlspecialchars($valueENT_QUOTES'UTF-8') . '"';
  2434.                         }
  2435.                     } elseif ($allowedAttr === 'target') {
  2436.                         // Only allow safe target values
  2437.                         if (in_array($value, ['_blank''_self''_parent''_top'])) {
  2438.                             $validAttrs[] = $allowedAttr '="' htmlspecialchars($valueENT_QUOTES'UTF-8') . '"';
  2439.                         }
  2440.                     } else {
  2441.                         // For other attributes, just escape the value
  2442.                         $validAttrs[] = $allowedAttr '="' htmlspecialchars($valueENT_QUOTES'UTF-8') . '"';
  2443.                     }
  2444.                 }
  2445.             }
  2446.             return '<' $tag . (empty($validAttrs) ? '' ' ' implode(' '$validAttrs)) . '>';
  2447.         }, $html);
  2448.         // Step 8: Remove any malformed or incomplete tags
  2449.         $html preg_replace('/<[^>]*$/'''$html); // Remove incomplete tags at end
  2450.         $html preg_replace('/^[^<]*>/'''$html); // Remove incomplete tags at start
  2451.         // Step 9: Final security check - remove any remaining suspicious patterns
  2452.         $suspiciousPatterns = [
  2453.             '/expression\s*\(/i',           // CSS expression()
  2454.             '/behavior\s*:/i',              // CSS behavior
  2455.             '/binding\s*:/i',               // CSS binding
  2456.             '/import\s*["\']?[^"\';]*;?/i'// CSS @import
  2457.             '/url\s*\([^)]*\)/i',          // CSS url()
  2458.             '/&#/i',                        // Remaining HTML entities
  2459.             '/&\w+;/i'                      // HTML entity patterns
  2460.         ];
  2461.         foreach ($suspiciousPatterns as $pattern) {
  2462.             $html preg_replace($pattern''$html);
  2463.         }
  2464.         // Step 10: Trim and return
  2465.         return trim($html);
  2466.     }
  2467.     public function getThreadAttachments($thread)
  2468.     {
  2469.         $attachments $this->entityManager->getRepository(CoreFrameworkEntity\Attachment::class)->findBy(['thread' => $thread->getId()]);
  2470.         $attachmentCollection array_map(function ($attachment) {
  2471.             return [
  2472.                 'id'                => $attachment->getId(),
  2473.                 'name'              => $attachment->getName(),
  2474.                 'contentType'       => $attachment->getContentType(),
  2475.                 'path'              => $attachment->getPath(),
  2476.                 'fileSystem'        => $attachment->getFileSystem(),
  2477.                 'attachmentThumb'   => $attachment->getAttachmentThumb(),
  2478.                 'attachmentOrginal' => $attachment->getAttachmentOrginal(),
  2479.             ];
  2480.         }, $attachments);
  2481.         return $attachmentCollection;
  2482.     }
  2483.     public function sendWebhookNotificationAction($thread)
  2484.     {
  2485.         $website $this->entityManager->getRepository(Website::class)->findOneByCode('helpdesk');
  2486.         $endpoint $website->getWebhookUrl();
  2487.         $customFields = [];
  2488.         if (empty($endpoint)) {
  2489.             return;
  2490.         }
  2491.         if ($thread->getThreadType() == 'reply' || $thread->getThreadType() == 'create') {
  2492.             $ticket $thread->getTicket();
  2493.             if ($this->userService->isFileExists('apps/uvdesk/custom-fields')) {
  2494.                 $customFieldsService $this->container->get('uvdesk_package_custom_fields.service');
  2495.                 $customFields =  $customFieldsService->getTicketCustomFieldDetails($ticket->getId());
  2496.             }
  2497.             $ticketStatus = [
  2498.                 'id'    => $ticket->getStatus()->getId(),
  2499.                 'code'  => $ticket->getStatus()->getCode(),
  2500.             ];
  2501.             $payload json_encode([
  2502.                 'threadDetails' => [
  2503.                     'threadId'          => $thread->getId(),
  2504.                     'threadReply'       => strip_tags(html_entity_decode($thread->getMessage())),
  2505.                     'ticketId'          => $ticket->getId(),
  2506.                     'customerEmail'     => $ticket->getCustomer()->getEmail(),
  2507.                     'userType'          => $thread->getCreatedBy(),
  2508.                     'agentEmail'        => $thread->getCreatedBy() === 'agent' $thread->getUser()->getEmail() : '',
  2509.                     'createdAt'         => $thread->getCreatedAt(),
  2510.                     'source'            => $thread->getSource(),
  2511.                     'threadAttachments' => $this->getThreadAttachments($thread),
  2512.                     'status'            => $ticketStatus,
  2513.                 ],
  2514.                 'customFields' => $customFields
  2515.             ]);
  2516.             $curlHandler curl_init();
  2517.             curl_setopt($curlHandlerCURLOPT_URL$endpoint);
  2518.             curl_setopt($curlHandlerCURLOPT_RETURNTRANSFER1);
  2519.             curl_setopt($curlHandlerCURLOPT_HTTPHEADER, ['Content-Type: application/json']);
  2520.             curl_setopt($curlHandlerCURLOPT_POSTtrue);
  2521.             curl_setopt($curlHandlerCURLOPT_POSTFIELDS$payload);
  2522.             $curlResponse curl_exec($curlHandler);
  2523.             curl_close($curlHandler);
  2524.         }
  2525.         return;
  2526.     }
  2527. }