vendor/uvdesk/core-framework/Controller/Thread.php line 107

Open in your IDE?
  1. <?php
  2. namespace Webkul\UVDesk\CoreFrameworkBundle\Controller;
  3. use Symfony\Component\HttpFoundation\Request;
  4. use Symfony\Component\HttpFoundation\Response;
  5. use Symfony\Component\EventDispatcher\GenericEvent;
  6. use Webkul\UVDesk\CoreFrameworkBundle\Entity\Ticket;
  7. use Symfony\Component\EventDispatcher\EventDispatcherInterface;
  8. use Webkul\UVDesk\CoreFrameworkBundle\Entity\Attachment;
  9. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
  10. use Webkul\UVDesk\CoreFrameworkBundle\Entity\TicketStatus;
  11. use UVDesk\CommunityPackages\UVDesk\CustomFields\Form\CustomFieldsType;
  12. use UVDesk\CommunityPackages\UVDesk\CustomFields\Entity\CustomFieldsValues;
  13. use UVDesk\CommunityPackages\UVDesk\CustomFields\Entity\CustomFields;
  14. use UVDesk\CommunityPackages\UVDesk\CustomFields\Entity\Type;
  15. use UVDesk\CommunityPackages\UVDesk\CustomFields\Entity\TicketCustomFieldsValues;
  16. use Webkul\UVDesk\CoreFrameworkBundle\Entity\Thread as TicketThread;
  17. use Webkul\UVDesk\CoreFrameworkBundle\Workflow\Events as CoreWorkflowEvents;
  18. use Webkul\UVDesk\CoreFrameworkBundle\Services\UserService;
  19. use Symfony\Contracts\Translation\TranslatorInterface;
  20. use Webkul\UVDesk\CoreFrameworkBundle\Services\UVDeskService;
  21. use Webkul\UVDesk\CoreFrameworkBundle\Services\TicketService;
  22. use Webkul\UVDesk\CoreFrameworkBundle\Services\EmailService;
  23. use Symfony\Component\HttpKernel\KernelInterface;
  24. use Webkul\UVDesk\CoreFrameworkBundle\Services\FileUploadService;
  25. use UVDesk\CommunityPackages\UVDesk\CustomFields\Services\CustomFieldsService;
  26. use Symfony\Component\HttpFoundation\File\UploadedFile;
  27. class Thread extends AbstractController
  28. {
  29.     private $userService;
  30.     private $translator;
  31.     private $eventDispatcher;
  32.     private $ticketService;
  33.     private $emailService;
  34.     private $kernel;
  35.     private $fileUploadService;
  36.     private $customFieldsService;
  37.     
  38.     public function __construct(UserService $userServiceTranslatorInterface $translatorTicketService $ticketServiceEmailService $emailServiceEventDispatcherInterface $eventDispatcherKernelInterface $kernelFileUploadService $fileUploadServiceCustomFieldsService $customFieldsService)
  39.     {
  40.         $this->kernel $kernel;
  41.         $this->userService $userService;
  42.         $this->emailService $emailService;
  43.         $this->translator $translator;
  44.         $this->ticketService $ticketService;
  45.         $this->eventDispatcher $eventDispatcher;
  46.         $this->fileUploadService $fileUploadService;
  47.         $this->customFieldsService $customFieldsService;
  48.     }
  49.     public function saveThread($ticketIdRequest $request)
  50.     {
  51.         $params $request->request->all();
  52.         $entityManager $this->getDoctrine()->getManager();
  53.         $ticket $entityManager->getRepository(Ticket::class)->findOneById($ticketId);
  54.         // Proceed only if user has access to the resource
  55.         if (false == $this->ticketService->isTicketAccessGranted($ticket)) {
  56.             throw new \Exception('Access Denied'403);
  57.         }
  58.         if (empty($ticket)) {
  59.             throw new \Exception('Ticket not found'404);
  60.         } else if ('POST' !== $request->getMethod()) {
  61.             throw new \Exception('Invalid Request'403);
  62.         }
  63.         if (empty($params)) {
  64.             return $this->redirect($request->headers->get('referer') ?: $this->generateUrl('helpdesk_member_ticket_collection'));
  65.         } else if ('note' == $params['threadType'] && false == $this->userService->isAccessAuthorized('ROLE_AGENT_ADD_NOTE')) {
  66.             // Insufficient user privilege to create a note
  67.             throw new \Exception('Insufficient Permisions'400);
  68.         }
  69.         // // Deny access unles granted ticket view permission
  70.         // $this->denyAccessUnlessGranted('AGENT_VIEW', $ticket);
  71.         // Check if reply content is empty
  72.         $parsedMessage trim(strip_tags($params['reply'], '<img>'));
  73.         $parsedMessage str_replace('&nbsp;'''$parsedMessage);
  74.         $parsedMessage str_replace(' '''$parsedMessage);
  75.         
  76.         $timeSpent trim(strip_tags($params['time_spent']));
  77.         $timeSpent str_replace('&nbsp;'''$timeSpent);
  78.         
  79.         $timeSpent str_replace(' '''$timeSpent);
  80.         if (null == $parsedMessage) {
  81.             $this->addFlash('warning'$this->translator->trans('Reply content cannot be left blank.'));
  82.         }
  83.         // @TODO: Validate file attachments
  84.         // if (true !== $this->get('file.service')->validateAttachments($request->files->get('attachments'))) {
  85.         //     $this->addFlash('warning', "Invalid attachments.");
  86.         // }
  87.          
  88.         $sanitizedReply htmlspecialchars($params['reply']);
  89.         // Initialize the parsed message with the sanitized reply followed by two <br> tags
  90.         $parsedMessage $sanitizedReply '<br><br>';
  91.         // // Check if customFields is present in the params array and is an array
  92.         // if (isset($params['customFields']) && is_array($params['customFields'])) {
  93.         //     // Iterate through each custom field
  94.         //     foreach ($params['customFields'] as $key => $value) {
  95.         //         // Only process non-empty custom fields
  96.         //         if (!empty($value)) {
  97.         //             // Sanitize the custom field value
  98.         //             $sanitizedValue = htmlspecialchars(trim(strip_tags($value, '<img>')));
  99.                     
  100.         //             // Append the sanitized custom field value to the parsed message with a new line
  101.         //             $parsedMessage .= $sanitizedValue . '<br>';
  102.         //         }
  103.         //     }
  104.         // }
  105.         
  106.         // $adminReply =  str_replace(['<p>','</p>'],"",$params['reply']);
  107.          $typevalue htmlspecialchars($params['typeValue']);
  108.         $threadDetails = [
  109.             'user' => $this->getUser(),
  110.             'createdBy' => 'agent',
  111.             'source' => 'website',
  112.             'threadType' => strtolower($params['threadType']),
  113.             'message' => str_replace(['&lt;script&gt;''&lt;/script&gt;'], ''$parsedMessage),
  114.             'attachments' => $request->files->get('attachments'),
  115.             
  116.             'timeSpent' => $timeSpent,
  117.         ];
  118.         if(!empty($params['status'])){
  119.             $ticketStatus $entityManager->getRepository(TicketStatus::class)->findOneByCode($params['status']);
  120.             $ticket->setStatus($ticketStatus);
  121.         }
  122.         if (isset($params['to'])) {
  123.             $threadDetails['to'] = $params['to'];
  124.         }
  125.         if (isset($params['cc'])) {
  126.             $threadDetails['cc'] = $params['cc'];
  127.         }
  128.         if (isset($params['cccol'])) {
  129.             $threadDetails['cccol'] = $params['cccol'];
  130.         }
  131.         if (isset($params['typeValue'])) {
  132.             $threadDetails['typeId'] = $params['typeValue'];
  133.         }
  134.         if (isset($params['bcc'])) {
  135.             $threadDetails['bcc'] = $params['bcc'];
  136.         }
  137.         
  138.         
  139.         // Create Thread
  140.         $thread $this->ticketService->createThread($ticket$threadDetails);
  141.         // $this->addFlash('success', ucwords($params['threadType']) . " added successfully.");
  142.         // @TODO: Remove Agent Draft Thread
  143.         // @TODO: Trigger Thread Created Event
  144.         // @TODO: Cross Review
  145.         // check for thread types
  146.         switch ($thread->getThreadType()) {
  147.             case 'note':
  148.                 $event = new GenericEvent(CoreWorkflowEvents\Ticket\Note::getId(), [
  149.                     'entity' =>  $ticket,
  150.                     'thread' =>  $thread
  151.                 ]);
  152.                 $this->eventDispatcher->dispatch($event'uvdesk.automation.workflow.execute');
  153.                 // @TODO: Render response on the basis of event response (if propogation was stopped or not)
  154.                 $this->addFlash('success'$this->translator->trans('Note added to ticket successfully.'));
  155.                 break;
  156.             case 'reply':
  157.                     // ✅ Step 1: Save the thread first
  158.                     //$thread = $this->ticketService->createThread($ticket, $threadDetails);
  159.                     //$entityManager->persist($thread);
  160.                    //$entityManager->flush(); // ✅ Ensure thread_id is saved before proceeding
  161.                 
  162.                     // ✅ Step 2: Now save custom field values
  163.                     if (!empty($params['customFields'])) {
  164.                         foreach ($params['customFields'] as $customFieldId => $customFieldValue) {
  165.                             $customField $entityManager->getRepository(CustomFields::class)->findOneById($customFieldId);
  166.                             if (!$customField) {
  167.                                 continue; // Skip invalid fields
  168.                             }
  169.                 
  170.                             // Check if the value already exists for this thread and custom field
  171.                             $ticketCustomFieldValue $entityManager->getRepository(TicketCustomFieldsValues::class)
  172.                             ->findOneBy(['ticket' => $ticket'ThreadId' => $thread->getId(), 'ticketCustomFieldsValues' => $customFieldId]);
  173.                             if (!$ticketCustomFieldValue) {
  174.                             $ticketCustomFieldValue = new TicketCustomFieldsValues();
  175.                             $ticketCustomFieldValue->setTicket($ticket);
  176.                             $ticketCustomFieldValue->setThreadId($thread->getId());
  177.                             $ticketCustomFieldValue->setTicketCustomFieldsValues($customField);
  178.                             }
  179.                 
  180.                             // Handle multiple values (checkbox, multi-select)
  181.                             if (is_array($customFieldValue)) {
  182.                                 $ticketCustomFieldValue->setValue(json_encode($customFieldValue));
  183.                             } else {
  184.                                 $ticketCustomFieldValue->setValue($customFieldValue);
  185.                             }
  186.                 
  187.                             $entityManager->persist($ticketCustomFieldValue);
  188.                         }
  189.                     }
  190.                 
  191.                     // ✅ Step 3: Flush to save custom fields before workflow execution
  192.                     $entityManager->flush();
  193.                 
  194.                     // ✅ Step 4: Fetch updated custom fields after saving
  195.                     $ticketCustomFieldsValues $entityManager->getRepository(TicketCustomFieldsValues::class)->findBy(['ticket' => $ticket]);
  196.                     $customFieldsArray = [];
  197.                     foreach ($ticketCustomFieldsValues as $fieldValue) {
  198.                         $customFieldsArray[$fieldValue->getTicketCustomFieldsValues()->getId()] = $fieldValue->getValue();
  199.                     }
  200.                 
  201.                     // ✅ Step 5: Now dispatch workflow event (after saving thread & custom fields)
  202.                     $event = new GenericEvent(CoreWorkflowEvents\Ticket\AgentReply::getId(), [
  203.                         'entity' =>  $ticket,
  204.                         'thread' =>  $thread,
  205.                         'customfields' => $customFieldsArray
  206.                     ]);
  207.                     $this->eventDispatcher->dispatch($event'uvdesk.automation.workflow.execute');
  208.                 
  209.                     $this->addFlash('success'$this->translator->trans('Success! Reply added successfully.'));
  210.                     break;
  211.             case 'forward':
  212.                 // Prepare headers
  213.                 $headers = ['References' => $ticket->getReferenceIds()];
  214.                 if (null != $ticket->currentThread->getMessageId()) {
  215.                     $headers['In-Reply-To'] = $ticket->currentThread->getMessageId();
  216.                 }
  217.                 // Prepare attachments
  218.                 $attachments $entityManager->getRepository(Attachment::class)->findByThread($thread);
  219.                 $projectDir $this->kernel->getProjectDir();
  220.                 $attachments array_map(function($attachment) use ($projectDir) {
  221.                 return str_replace('//''/'$projectDir "/public" $attachment->getPath());
  222.                 }, $attachments);
  223.                 $message '<html><body style="background-image: none"><p>'.html_entity_decode($thread->getMessage()).'</p></body></html>';
  224.                 // Forward thread to users
  225.                 try {
  226.                     $messageId $this->emailService->sendMail($params['subject'] ?? ("Forward: " $ticket->getSubject()), $message$thread->getReplyTo(), $headers$ticket->getMailboxEmail(), $attachments ?? [], $thread->getCc() ?: [], $thread->getBcc() ?: []);
  227.     
  228.                     if (!empty($messageId)) {
  229.                         $thread->setMessageId($messageId);
  230.     
  231.                         $entityManager->persist($createdThread);
  232.                         $entityManager->flush();
  233.                     }
  234.                 } catch (\Exception $e) {
  235.                     // Do nothing ...
  236.                     // @TODO: Log exception
  237.                 }
  238.                 // @TODO: Render response on the basis of event response (if propogation was stopped or not)
  239.                 $this->addFlash('success'$this->translator->trans('Reply added to the ticket and forwarded successfully.'));
  240.                 break;
  241.             default:
  242.                 break;
  243.         }
  244.         $params['threadId'] = $thread->getId();
  245.         $ThreadId  $params['threadId'];
  246.         // Check if ticket status needs to be updated
  247.         $updateTicketToStatus = !empty($params['status']) ? (trim($params['status']) ?: null) : null;
  248.         if (!empty($updateTicketToStatus) && $this->userService->isAccessAuthorized('ROLE_AGENT_UPDATE_TICKET_STATUS')) {
  249.             $ticketStatus $entityManager->getRepository(TicketStatus::class)->findOneById($updateTicketToStatus);
  250.             if (!empty($ticketStatus) && $ticketStatus->getId() === $ticket->getStatus()->getId()) {
  251.                 $ticket->setStatus($ticketStatus);
  252.                 $entityManager->persist($ticket);
  253.                 $entityManager->flush();
  254.                 // @TODO: Trigger Ticket Status Updated Event
  255.             }
  256.         }
  257.         
  258.         if (!empty($ticket)) {
  259.             $submittedCustomFields $request->request->get('customFields');
  260.             $submittedCustomFileFields $request->files->get('customFields');
  261.             $validationStatus $this->customFieldsService->customFieldsValidationWithoutRequired($request$request->request->get('area') ?: 'user');
  262.             if (!empty($validationStatus) && $validationStatus['errorMain'] == false && empty($validationStatus['formErrors'])) {
  263.                 $customFieldRepository $entityManager->getRepository(CustomFields::class);
  264.                 $ticketCustomFieldsValuesCollection $entityManager->getRepository(TicketCustomFieldsValues::class)->find($ticket);
  265.        
  266.                 if (!empty($submittedCustomFields)) {
  267.                     foreach ($submittedCustomFields as $customFieldId => $customFieldValue) {
  268.                         $existingCustomFieldValue null;
  269.                         
  270.                         // 1) Check existing records for this field
  271.                         if (!empty($ticketCustomFieldsValuesCollection)) {
  272.                             foreach ($ticketCustomFieldsValuesCollection as $ticketCustomField) {
  273.                                 if ($ticketCustomField->getTicketCustomFieldsValues()->getId() == $customFieldId) {
  274.                                     $existingCustomFieldValue $ticketCustomField;
  275.                                     break;
  276.                                 }
  277.                             }
  278.                         }
  279.                 
  280.                         // 2) Fetch the definition of this custom field
  281.                         $customField $customFieldRepository->findOneById($customFieldId);
  282.                 
  283.                         // 3) Reuse or create a new TicketCustomFieldsValues entity
  284.                         $ticketCustomFieldValue $existingCustomFieldValue ?: new TicketCustomFieldsValues();
  285.                 
  286.                         // 4) Associate this value with the correct Ticket + Field
  287.                         $ticketCustomFieldValue->setTicket($ticket);
  288.                         $ticketCustomFieldValue->setTicketCustomFieldsValues($customField);
  289.                 
  290.                         // 5) Handle possible multiple checkbox selections
  291.                         if (is_array($customFieldValue)) {
  292.                             // Option A: Convert array to JSON
  293.                             $ticketCustomFieldValue->setValue(json_encode($customFieldValue));
  294.                 
  295.                             // or Option B: Convert to comma‐separated string
  296.                             // $ticketCustomFieldValue->setValue(implode(',', $customFieldValue));
  297.                         } else {
  298.                             // It's a single value (string)
  299.                             $ticketCustomFieldValue->setValue($customFieldValue);
  300.                         }
  301.                 
  302.                         // 6) Set thread ID if needed
  303.                         $ticketCustomFieldValue->setThreadId($thread->getId());
  304.                 
  305.                         // 7) Persist changes
  306.                         $entityManager->persist($ticketCustomFieldValue);
  307.                         $entityManager->persist($ticket);
  308.                     }
  309.                 }                
  310.                 
  311.                 if (!empty($submittedCustomFileFields)) {
  312.                     
  313.                     $baseUploadPath '/custom-fields/ticket/' $ticket->getId() . '/';
  314.                     $temporaryFiles $request->files->get('customFields');
  315.                     
  316.                     if (!empty($temporaryFiles)) {
  317.                         foreach ($temporaryFiles as $key => $temporaryFile) {
  318.                             // ✅ Ensure $temporaryFile is actually uploaded before processing
  319.                             if ($temporaryFile instanceof UploadedFile) {
  320.                                 $fileName $this->fileUploadService->uploadFile($temporaryFile$baseUploadPathtrue);
  321.                                 $fileName['key'] = $key;
  322.                                 $uploadedFileCollection[] = $fileName;
  323.                             } else {
  324.                                 error_log("File not uploaded for key: " $key);
  325.                             }
  326.                         }
  327.                     } else {
  328.                         error_log("No file uploaded in this workflow step.");
  329.                     }
  330.        
  331.                     if (!empty($uploadedFileCollection)) {
  332.                         foreach ($uploadedFileCollection as $uploadedFile) {
  333.                             $existingCustomFieldValue null;
  334.                             if (!empty($ticketCustomFieldsValuesCollection)) {
  335.                                 foreach ($ticketCustomFieldsValuesCollection as $ticketCustomField) {
  336.                                     if ($ticketCustomField->getTicketCustomFieldsValues()->getId() == $uploadedFile['key']) {
  337.                                         $existingCustomFieldValue $ticketCustomField;
  338.                                         break;
  339.                                     }
  340.                                 }
  341.                             }
  342.        
  343.                             if(!empty($attachment)) {
  344.                                 $thread $attachment->getThread();
  345.                             }        
  346.         
  347.                             $thread = ($thread == null $ticket->getThreads()->get(0) : $thread);        
  348.        
  349.                             $uploadedAttachment $this->customFieldsService->addFilesEntryToAttachmentTable([$uploadedFile], $thread);
  350.                             if (!empty($uploadedAttachment[0])) {
  351.                                 $customField $customFieldRepository->findOneById($uploadedFile['key']);
  352.                                 $ticketCustomFieldValue = !empty($existingCustomFieldValue) ? $existingCustomFieldValue : new TicketCustomFieldsValues();
  353.                                 // $ticketCustomFieldValue->setValue($resourceURL);
  354.                                 $ticketCustomFieldValue->setValue(json_encode(['name' => $uploadedAttachment[0]['name'], 'path' => $uploadedAttachment[0]['path'], 'id' => $uploadedAttachment[0]['id']]));
  355.                                 $ticketCustomFieldValue->setTicketCustomFieldsValues($customField);
  356.                                 $ticketCustomFieldValue->setTicket($ticket);
  357.        
  358.                                 $entityManager->persist($ticketCustomFieldValue);
  359.                                 $entityManager->persist($ticket);
  360.                             }
  361.                         }
  362.                     }
  363.                 }
  364.        
  365.                 //$entityManager->flush();
  366.        
  367.                 $ticketCustomFieldsValuesCollection $entityManager->getRepository(TicketCustomFieldsValues::class)->findBy(['ticket' => $ticket]);
  368.                 if (!empty($ticketCustomFieldsValuesCollection)) {
  369.                     $ticketCustomFieldArrayCollection = [];
  370.        
  371.                     foreach ($ticketCustomFieldsValuesCollection as $ticketCustomField) {
  372.                         $ticketCustomFieldArrayCollection[$ticketCustomField->getTicketCustomFieldsValues()->getId()] = [
  373.                             'id' => $ticketCustomField->getId(),
  374.                             'encrypted' => $ticketCustomField->getEncrypted() ? true false,
  375.                             'targetCustomField' => $ticketCustomField->getTicketCustomFieldsValues()->getId(),
  376.                         ];
  377.        
  378.                         switch ($ticketCustomField->getTicketCustomFieldsValues()->getFieldType()) {
  379.                             case 'select':
  380.                             case 'radio':
  381.                             case 'checkbox':
  382.                                 $fieldId = [];
  383.                                 $fieldValue = [];
  384.        
  385.                                 if ($ticketCustomField->getEncrypted()) {
  386.                                     $ticketCustomField->decryptEntity();
  387.                                 }
  388.        
  389.                                 $fieldOptions json_decode($ticketCustomField->getValue(), true);
  390.        
  391.                                 if (empty($fieldOptions)) {
  392.                                     $fieldOptions explode(','$ticketCustomField->getValue());
  393.                                 } else {
  394.                                     if (!is_array($fieldOptions)) {
  395.                                         $fieldOptions = [$fieldOptions];
  396.                                     }
  397.                                 }
  398.        
  399.                                 foreach ($ticketCustomField->getTicketCustomFieldsValues()->getCustomFieldValues() as $multipleFieldValue) {
  400.                                     if (in_array($multipleFieldValue->getId(), $fieldOptions)) {
  401.                                         $fieldId[] = $multipleFieldValue->getId();
  402.                                         $fieldValue[] = $multipleFieldValue->getName();
  403.                                     }
  404.                                 }
  405.        
  406.                                 $ticketCustomFieldArrayCollection[$ticketCustomField->getTicketCustomFieldsValues()->getId()]['valueId'] = $fieldId;
  407.                                 $ticketCustomFieldArrayCollection[$ticketCustomField->getTicketCustomFieldsValues()->getId()]['value'] = $ticketCustomField->getEncrypted() ? nullimplode('</br>'$fieldValue);
  408.                                 break;
  409.                             default:
  410.                                 $ticketCustomFieldArrayCollection[$ticketCustomField->getTicketCustomFieldsValues()->getId()]['value'] = (!$ticketCustomField->getEncrypted()
  411.                                     ? (is_array(trim($ticketCustomField->getValue(), '"'))
  412.                                         ? json_encode(trim($ticketCustomField->getValue(), '"'))
  413.                                         : strip_tags(htmlentities(trim($ticketCustomField->getValue(), '"')))
  414.                                     )
  415.                                     : null
  416.                                 );
  417.                                 break;
  418.                         }
  419.                     }
  420.        
  421.                     $responseContent = [
  422.                         'success' => true,
  423.                         'ticketCustomFieldsValuesCollection' => $ticketCustomFieldArrayCollection,
  424.                     ];
  425.                 } else {
  426.                     $responseContent = [
  427.                         'success' => true,
  428.                         'ticketCustomFieldsValuesCollection' => [],
  429.                     ];
  430.                 }
  431.             } else {
  432.                 $responseContent = [
  433.                     'success' => false,
  434.                     'message' => $validationStatus['formErrors'],
  435.                     'ticketCustomFieldsValuesCollection' => [],
  436.                 ];
  437.             }
  438.         }
  439.          // Redirect to either Ticket View | Ticket Listings
  440.          if ('redirect' === $params['nextView']) {
  441.             return $this->redirect($this->generateUrl('helpdesk_member_ticket_collection'));
  442.         }
  443.         if (!empty($params['status']) && !empty($params['threadId'])) {
  444.             // Fetch the correct thread by ID
  445.             $thread $entityManager->getRepository(TicketThread::class)->find($params['threadId']);
  446.         
  447.             if ($thread) {
  448.                 // Update only the specific thread's status_update
  449.                 $thread->setStatusUpdate($params['status']);
  450.                 $entityManager->persist($thread);
  451.                 $entityManager->flush();
  452.             } else {
  453.                 // Optional: Handle the case where the thread ID does not exist
  454.                 throw new \Exception("Thread ID " $params['threadId'] . " not found.");
  455.             }
  456.         }
  457.         
  458.         
  459.         return $this->redirect($this->generateUrl('helpdesk_member_ticket', ['ticketId' => $ticket->getId()]));
  460.     }
  461.     public function updateThreadXHR(Request $request)
  462.     {
  463.         $json = [];
  464.         $em $this->getDoctrine()->getManager();
  465.         $content json_decode($request->getContent(), true);
  466.         $thread $em->getRepository(TicketThread::class)->find($request->attributes->get('threadId'));
  467.         $ticket $thread->getTicket();
  468.         $user $this->userService->getSessionUser();
  469.         // Proceed only if user has access to the resource
  470.         if ( (!$this->userService->getSessionUser()) || (false == $this->ticketService->isTicketAccessGranted($ticket$user)) ) 
  471.         {
  472.             throw new \Exception('Access Denied'403);
  473.         }
  474.               // Assuming 'thread_id' and 'customfields' are part of the $content array
  475.               $threadId $content['id'];
  476.               $customFields $content['customfields'];
  477.   
  478.   
  479.   
  480.               foreach ($customFields as $customField) {
  481.   
  482.                   if (isset($customField['custom_field_id']) && isset($customField['value'])) {
  483.   
  484.                       $customFieldId $customField['custom_field_id'];
  485.   
  486.                       $value $customField['value'];
  487.                       // Convert array to JSON string if it's an array
  488.                         if (is_array($value)) {
  489.                             $customField['value'] = json_encode($value);
  490.                             $value $customField['value'];
  491.                         }
  492.                         
  493.   
  494.                       // Find the existing custom field value record
  495.   
  496.                       $customFieldValue $em->getRepository(TicketCustomFieldsValues::class)
  497.   
  498.                           ->findOneBy([
  499.   
  500.                               'ThreadId' => $threadId,
  501.   
  502.                               'ticketCustomFieldsValues' => $customFieldId
  503.   
  504.                           ]);
  505.   
  506.   
  507.   
  508.                       // If the record exists, update the value
  509.   
  510.                       if ($customFieldValue) {
  511.   
  512.                           $customFieldValue->setValue($value);
  513.   
  514.                           $em->persist($customFieldValue);
  515.   
  516.                           $em->flush();  
  517.   
  518.                       }
  519.   
  520.                   }
  521.   
  522.               }
  523.   
  524.   
  525.   
  526.               // // Flush changes to the database
  527.   
  528.               // $this->$em->flush();      
  529.               if ($request->getMethod() == "PUT") {
  530.                 // $this->isAuthorized('ROLE_AGENT_EDIT_THREAD_NOTE');
  531.     
  532.                 if (str_replace(' ','',str_replace('&nbsp;','',trim(strip_tags($content['reply'], '<img>')))) != "") {
  533.     
  534.                     $thread->setMessage($content['reply']);
  535.     
  536.                     $em->persist($thread);
  537.     
  538.                     $em->flush();
  539.     
  540.     
  541.     
  542.                     $ticket->currentThread $thread;
  543.     
  544.     
  545.     
  546.                     // Trigger agent reply event
  547.     
  548.                     $event = new GenericEvent(CoreWorkflowEvents\Ticket\ThreadUpdate::getId(), [
  549.     
  550.                         'entity' =>  $ticket,
  551.     
  552.                     ]);
  553.     
  554.     
  555.     
  556.                     $this->eventDispatcher->dispatch($event'uvdesk.automation.workflow.execute');
  557.     
  558.     
  559.     
  560.                     $json['alertMessage'] = $this->translator->trans('Success ! Thread updated successfully.');
  561.     
  562.                     $json['alertClass'] = 'success';
  563.                     $json['reload'] = true// Add this flag to indicate a page reload
  564.     
  565.                 } else {
  566.     
  567.                     $json['alertMessage'] = $this->translator->trans('Error ! Reply field can not be blank.');
  568.     
  569.                     $json['alertClass'] = 'error';
  570.     
  571.                 }
  572.     
  573.             }
  574.     
  575.     
  576.     
  577.             return new Response(json_encode($json), 200, ['Content-Type' => 'application/json']);
  578.         }
  579.     
  580.     
  581.     
  582.         public function threadXHR(Request $request)
  583.     
  584.         {
  585.     
  586.             $json = array();
  587.     
  588.             $content json_decode($request->getContent(), true);
  589.     
  590.             $em $this->getDoctrine()->getManager();
  591.     
  592.     
  593.     
  594.             $ticket $em->getRepository(Ticket::class)->findOneById($content['ticketId']); 
  595.     
  596.     
  597.     
  598.             // Proceed only if user has access to the resource
  599.     
  600.             if (false == $this->ticketService->isTicketAccessGranted($ticket)){
  601.     
  602.                 throw new \Exception('Access Denied'403);
  603.     
  604.             }
  605.     
  606.             
  607.     
  608.             $threadId $request->attributes->get('threadId');
  609.     
  610.     
  611.     
  612.             if ($request->getMethod() == "DELETE") {
  613.     
  614.                 $thread $em->getRepository(TicketThread::class)->findOneBy(array('id' => $threadId'ticket' => $content['ticketId']));
  615.     
  616.                 $projectDir $this->kernel->getProjectDir();
  617.     
  618.                 
  619.     
  620.                 if ($thread) {
  621.     
  622.                     $this->fileUploadService->fileRemoveFromFolder($projectDir."/public/assets/threads/".$threadId);
  623.     
  624.                     // Trigger thread deleted event
  625.     
  626.                     //  $event = new GenericEvent(CoreWorkflowEvents\Ticket\ThreadUpdate::getId(), [
  627.     
  628.                     //     'entity' =>  $ticket,
  629.     
  630.                     // ]);
  631.     
  632.                     // $this->eventDispatcher->dispatch('uvdesk.automation.workflow.execute', $event);
  633.     
  634.     
  635.     
  636.                     $em->remove($thread);
  637.     
  638.                     $em->flush();
  639.     
  640.                     $json['alertClass'] = 'success';
  641.     
  642.                     $json['alertMessage'] = $this->translator->trans('Success ! Thread removed successfully.');
  643.     
  644.                 } else {
  645.     
  646.                     $json['alertClass'] = 'danger';
  647.     
  648.                     $json['alertMessage'] = $this->translator->trans('Error ! Invalid thread.');
  649.     
  650.                 }
  651.     
  652.             } elseif ($request->getMethod() == "PATCH") {
  653.     
  654.                 $thread $em->getRepository(TicketThread::class)->findOneBy(array('id' => $request->attributes->get('threadId'), 'ticket' => $content['ticketId']));
  655.     
  656.     
  657.     
  658.                 if ($thread) {
  659.     
  660.                     if ($content['updateType'] == 'lock') {
  661.     
  662.                         $thread->setIsLocked($content['isLocked']);
  663.     
  664.                         $em->persist($thread);
  665.     
  666.                         $em->flush();
  667.     
  668.     
  669.     
  670.                         $json['alertMessage'] = $this->translator->trans($content['isLocked'] ? 'Success ! Thread locked successfully.' 'Success ! Thread unlocked successfully.');
  671.     
  672.                         $json['alertClass'] = 'success';
  673.     
  674.                     } elseif ($content['updateType'] == 'bookmark') {
  675.     
  676.                         $thread->setIsBookmarked($content['bookmark']);
  677.     
  678.                         $em->persist($thread);
  679.     
  680.                         $em->flush();
  681.     
  682.     
  683.     
  684.                         $json['alertMessage'] = $this->translator->trans($content['bookmark'] ? 'Success ! Thread pinned successfully.' 'Success ! unpinned removed successfully.');
  685.     
  686.                         $json['alertClass'] = 'success';
  687.     
  688.                     }
  689.     
  690.                 } else {
  691.     
  692.                     $json['alertClass'] = 'danger';
  693.     
  694.                     $json['alertMessage'] = $this->translator->trans('Error ! Invalid thread.');
  695.     
  696.                 }
  697.     
  698.             }
  699.     
  700.     
  701.     
  702.             return new Response(json_encode($json), 200, ['Content-Type' => 'application/json']);
  703.     
  704.         }
  705.     
  706.     }