src/EventListener/TicketReplyWebhookSubscriber.php line 47

Open in your IDE?
  1. <?php
  2. namespace App\EventListener;
  3. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  4. use Symfony\Component\HttpClient\HttpClient;
  5. use Psr\Log\LoggerInterface;
  6. use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
  7. use App\Service\FirebaseNotificationService;
  8. use Webkul\UVDesk\CoreFrameworkBundle\Workflow\Events\Ticket\AgentReply;
  9. use Webkul\UVDesk\CoreFrameworkBundle\Workflow\Events\Ticket\CustomerReply;
  10. use Webkul\UVDesk\AutomationBundle\Workflow\Events\TicketActivity;
  11. /**
  12. * Custom event listener to send webhook/API requests to your backend
  13. * and Firebase push notifications when tickets are replied to.
  14. */
  15. class TicketReplyWebhookSubscriber implements EventSubscriberInterface
  16. {
  17. private $logger;
  18. private $httpClient;
  19. private $params;
  20. private $firebaseService;
  21. public function __construct(
  22. LoggerInterface $logger,
  23. ParameterBagInterface $params,
  24. FirebaseNotificationService $firebaseService
  25. ) {
  26. $this->logger = $logger;
  27. $this->params = $params;
  28. $this->httpClient = HttpClient::create();
  29. $this->firebaseService = $firebaseService;
  30. }
  31. public static function getSubscribedEvents()
  32. {
  33. return [
  34. // UVDesk dispatches all workflow events with this name
  35. 'uvdesk.automation.workflow.execute' => 'onWorkflowEvent',
  36. ];
  37. }
  38. /**
  39. * Handle workflow events - check if it's a ticket reply event
  40. */
  41. public function onWorkflowEvent($event)
  42. {
  43. // Log that we received an event
  44. $this->logger->debug('Webhook subscriber received workflow event', [
  45. 'event_class' => get_class($event),
  46. ]);
  47. // Only process ticket activity events
  48. if (!($event instanceof TicketActivity)) {
  49. $this->logger->debug('Event is not a TicketActivity, skipping');
  50. return;
  51. }
  52. // Check event type
  53. $eventId = $event::getId();
  54. $this->logger->debug('TicketActivity event detected', ['event_id' => $eventId]);
  55. if ($eventId === 'uvdesk.ticket.agent_reply') {
  56. $this->logger->info('Agent reply event detected, sending webhook and push notification');
  57. $this->sendWebhook('agent_reply', $event);
  58. $this->sendPushNotification($event, 'agent_reply');
  59. } elseif ($eventId === 'uvdesk.ticket.customer_reply') {
  60. $this->logger->info('Customer reply event detected, sending webhook');
  61. $this->sendWebhook('customer_reply', $event);
  62. // Optionally send notification to agent when customer replies
  63. // $this->sendPushNotification($event, 'customer_reply');
  64. } elseif ($eventId === 'uvdesk.ticket.collaborator_reply') {
  65. $this->logger->info('Collaborator reply event detected, sending webhook');
  66. $this->sendWebhook('collaborator_reply', $event);
  67. } else {
  68. $this->logger->debug('Event is not a reply event, skipping', ['event_id' => $eventId]);
  69. }
  70. }
  71. /**
  72. * Send webhook to your backend
  73. */
  74. private function sendWebhook(string $eventType, $event)
  75. {
  76. // Get webhook URL from environment variable
  77. $webhookUrl = $_ENV['WEBHOOK_BACKEND_URL'] ?? $this->params->get('env(WEBHOOK_BACKEND_URL)') ?? null;
  78. if (empty($webhookUrl)) {
  79. $this->logger->warning('Webhook URL not configured. Set WEBHOOK_BACKEND_URL in .env file.');
  80. return;
  81. }
  82. $this->logger->info('Sending webhook', ['url' => $webhookUrl, 'event_type' => $eventType]);
  83. try {
  84. // Extract ticket data from event
  85. $ticket = $event->getTicket() ?? null;
  86. $thread = $event->getThread() ?? null;
  87. if (!$ticket || !$thread) {
  88. $this->logger->warning('Ticket or thread data not available in event');
  89. return;
  90. }
  91. // Prepare payload to send to your backend
  92. $payload = [
  93. 'event_type' => $eventType,
  94. 'ticket_id' => $ticket->getId(),
  95. 'ticket_number' => 'TKT-' . str_pad($ticket->getId(), 6, '0', STR_PAD_LEFT), // Format: TKT-000001
  96. 'ticket_subject' => $ticket->getSubject(),
  97. 'thread_id' => $thread->getId(),
  98. 'message' => $thread->getMessage(),
  99. 'created_by' => $thread->getCreatedBy(),
  100. 'created_at' => $thread->getCreatedAt() ? $thread->getCreatedAt()->format('Y-m-d H:i:s') : null,
  101. 'customer_email' => $ticket->getCustomer() ? $ticket->getCustomer()->getEmail() : null,
  102. 'agent_email' => $ticket->getAgent() ? $ticket->getAgent()->getEmail() : null,
  103. ];
  104. // Send HTTP POST request to your backend
  105. $response = $this->httpClient->request('POST', $webhookUrl, [
  106. 'json' => $payload,
  107. 'headers' => [
  108. 'Content-Type' => 'application/json',
  109. 'X-Webhook-Source' => 'uvdesk',
  110. ],
  111. 'timeout' => 10, // 10 second timeout
  112. ]);
  113. $statusCode = $response->getStatusCode();
  114. if ($statusCode >= 200 && $statusCode < 300) {
  115. $this->logger->info('Webhook sent successfully', [
  116. 'event_type' => $eventType,
  117. 'ticket_id' => $ticket->getId(),
  118. 'status_code' => $statusCode,
  119. ]);
  120. } else {
  121. $this->logger->error('Webhook request failed', [
  122. 'event_type' => $eventType,
  123. 'ticket_id' => $ticket->getId(),
  124. 'status_code' => $statusCode,
  125. ]);
  126. }
  127. } catch (\Exception $e) {
  128. $this->logger->error('Error sending webhook', [
  129. 'event_type' => $eventType,
  130. 'error' => $e->getMessage(),
  131. ]);
  132. }
  133. }
  134. /**
  135. * Send Firebase push notification to customer when agent replies
  136. */
  137. private function sendPushNotification($event, string $eventType): void
  138. {
  139. try {
  140. $ticket = $event->getTicket() ?? null;
  141. $thread = $event->getThread() ?? null;
  142. if (!$ticket || !$thread) {
  143. return;
  144. }
  145. // Only send notification to customer when agent replies
  146. if ($eventType === 'agent_reply' && $ticket->getCustomer()) {
  147. $customerEmail = $ticket->getCustomer()->getEmail();
  148. if (empty($customerEmail)) {
  149. $this->logger->debug('No customer email found for push notification');
  150. return;
  151. }
  152. // Prepare notification data
  153. $ticketNumber = 'TKT-' . str_pad($ticket->getId(), 6, '0', STR_PAD_LEFT);
  154. $title = 'New Reply on Ticket #' . $ticketNumber;
  155. // Strip HTML tags for plain text description
  156. $description = strip_tags(html_entity_decode($thread->getMessage()));
  157. $description = trim($description) ?: 'You have a new reply on your ticket';
  158. // Create Quill Delta JSON format for preview
  159. // Format: [{"insert":"Message\n"}]
  160. $preview = json_encode([['insert' => $description . "\n"]]);
  161. // Additional data for deep linking
  162. $data = [
  163. 'type' => 'ticket_reply',
  164. 'ticket_id' => (string)$ticket->getId(),
  165. 'ticket_number' => $ticketNumber,
  166. 'thread_id' => (string)$thread->getId(),
  167. 'subject' => $ticket->getSubject() ?? '',
  168. ];
  169. // Send notification (saves to Firestore first, then sends FCM)
  170. $this->firebaseService->sendNotificationByEmail(
  171. $customerEmail,
  172. $title,
  173. $description,
  174. $preview,
  175. 'user', // topic
  176. $data
  177. );
  178. }
  179. } catch (\Exception $e) {
  180. $this->logger->error('Error sending push notification', [
  181. 'event_type' => $eventType,
  182. 'error' => $e->getMessage(),
  183. ]);
  184. }
  185. }
  186. }