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