<?php
namespace Webkul\UVDesk\CoreFrameworkBundle\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\JsonResponse;
use Webkul\UVDesk\CoreFrameworkBundle\Entity as CoreFrameworkBundleEntities;
use Webkul\UVDesk\CoreFrameworkBundle\Entity\SupportLabel;
use Symfony\Component\EventDispatcher\GenericEvent;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Webkul\UVDesk\CoreFrameworkBundle\Workflow\Events as CoreWorkflowEvents;
use Webkul\UVDesk\CoreFrameworkBundle\Form as CoreFrameworkBundleForms;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Webkul\UVDesk\CoreFrameworkBundle\DataProxies as CoreFrameworkBundleDataProxies;
class TicketXHR extends Controller
{
public function loadTicketXHR($ticketId)
{
$entityManager = $this->getDoctrine()->getManager();
$request = $this->container->get('request_stack')->getCurrentRequest();
}
public function bookmarkTicketXHR()
{
$entityManager = $this->getDoctrine()->getManager();
$request = $this->container->get('request_stack')->getCurrentRequest();
$requestContent = json_decode($request->getContent(), true);
$ticket = $entityManager->getRepository('UVDeskCoreFrameworkBundle:Ticket')->findOneById($requestContent['id']);
if (!empty($ticket)) {
$ticket->setIsStarred(!$ticket->getIsStarred());
$entityManager->persist($ticket);
$entityManager->flush();
return new Response(json_encode(['alertClass' => 'success']), 200, ['Content-Type' => 'application/json']);
}
return new Response(json_encode([]), 404, ['Content-Type' => 'application/json']);
}
public function ticketLabelXHR(Request $request)
{
$method = $request->getMethod();
$content = $request->getContent();
$em = $this->getDoctrine()->getManager();
if($method == "POST") {
$data = json_decode($content, true);
if($data['name'] != "") {
$label = new SupportLabel();
$label->setName($data['name']);
if(isset($data['colorCode']))
$label->setColorCode($data['colorCode']);
$label->setUser($this->get('user.service')->getCurrentUser());
$em->persist($label);
$em->flush();
$json['alertClass'] = 'success';
$json['alertMessage'] = $this->get('translator')->trans('Success ! Label created successfully.');
$json['label'] = json_encode([
'id' => $label->getId(),
'name' => $label->getName(),
'colorCode' => $label->getColorCode(),
'labelUser' => $label->getUser()->getId(),
]);
} else {
$json['alertClass'] = 'danger';
$json['alertMessage'] = $this->get('translator')->trans('Error ! Label name can not be blank.');
}
} elseif($method == "PUT") {
$data = json_decode($content, true);
$label = $em->getRepository('UVDeskCoreFrameworkBundle:SupportLabel')->findOneBy(array('id' => $request->attributes->get('ticketLabelId')));
if($label) {
$label->setName($data['name']);
if(!empty($data['colorCode'])) {
$label->setColorCode($data['colorCode']);
}
$em->persist($label);
$em->flush();
$json['label'] = json_encode([
'id' => $label->getId(),
'name' => $label->getName(),
'colorCode' => $label->getColorCode(),
'labelUser' => $label->getUser()->getId(),
]);
$json['alertClass'] = 'success';
$json['alertMessage'] = $this->get('translator')->trans('Success ! Label updated successfully.');
} else {
$json['alertClass'] = 'danger';
$json['alertMessage'] = $this->get('translator')->trans('Error ! Invalid label id.');
}
} elseif($method == "DELETE") {
$label = $em->getRepository('UVDeskCoreFrameworkBundle:SupportLabel')->findOneBy(array('id' => $request->attributes->get('ticketLabelId')));
if($label) {
$em->remove($label);
$em->flush();
$json['alertClass'] = 'success';
$json['alertMessage'] = $this->get('translator')->trans('Success ! Label removed successfully.');
} else {
$json['alertClass'] = 'danger';
$json['alertMessage'] = $this->get('translator')->trans('Error ! Invalid label id.');
}
}
return new Response(json_encode($json), 200, ['Content-Type' => 'application/json']);
}
public function updateTicketDetails(Request $request)
{
$ticketId = $request->attributes->get('ticketId');
$entityManager = $this->getDoctrine()->getManager();
$ticket = $entityManager->getRepository('UVDeskCoreFrameworkBundle:Ticket')->find($ticketId);
if (!$ticket)
$this->noResultFound();
$error = false;
$message = '';
if ($request->request->get('subject') == '') {
$error = true;
$message = $this->get('translator')->trans('Error! Subject field is mandatory');
} elseif ($request->request->get('reply') == '') {
$error = true;
$message = $this->get('translator')->trans('Error! Reply field is mandatory');
}
if (!$error) {
$ticket->setSubject($request->request->get('subject'));
$createThread = $this->get('ticket.service')->getCreateReply($ticket->getId(), false);
$createThread = $entityManager->getRepository('UVDeskCoreFrameworkBundle:Thread')->find($createThread['id']);
$createThread->setMessage($request->request->get('reply'));
$entityManager->persist($createThread);
$entityManager->persist($ticket);
$entityManager->flush();
$this->addFlash('success', $this->get('translator')->trans('Success ! Ticket has been updated successfully.'));
} else {
$this->addFlash('warning', $message);
}
return $this->redirect($this->generateUrl('helpdesk_member_ticket', ['ticketId'=> $ticketId] ));
}
public function updateTicketAttributes($ticketId)
{
// @TODO: Ticket Voter
// $this->denyAccessUnlessGranted('VIEW', $ticket);
$entityManager = $this->getDoctrine()->getManager();
$request = $this->container->get('request_stack')->getCurrentRequest();
$requestContent = $request->request->all() ?: json_decode($request->getContent(), true);
$ticketId = $ticketId != 0 ? $ticketId : $requestContent['ticketId'];
$ticket = $entityManager->getRepository('UVDeskCoreFrameworkBundle:Ticket')->findOneById($ticketId);
// Validate request integrity
if (empty($ticket)) {
$responseContent = [
'alertClass' => 'danger',
'alertMessage' => $this->get('translator')->trans('Unable to retrieve details for ticket #%ticketId%.', [
'%ticketId%' => $ticketId,
]),
];
return new Response(json_encode($responseContent), 200, ['Content-Type' => 'application/json']);
} else if (!isset($requestContent['attribute'])) {
$responseContent = [
'alertClass' => 'danger',
'alertMessage' => $this->get('translator')->trans('Insufficient details provided.'),
];
return new Response(json_encode($responseContent), 400, ['Content-Type' => 'application/json']);
}
// Update attribute
switch ($requestContent['attribute']) {
case 'agent':
if($requestContent['value'] == '0'){
$ticket->setAgent(null);
$entityManager->persist($ticket);
$entityManager->flush();
// Trigger Agent Assign event
$event = new GenericEvent(CoreWorkflowEvents\Ticket\Agent::getId(), [
'entity' => $ticket,
]);
$this->get('event_dispatcher')->dispatch('uvdesk.automation.workflow.execute', $event);
return new Response(json_encode([
'alertClass' => 'success',
'alertMessage' => $this->get('translator')->trans('Ticket successfully unassigned', [
'%agent%' => $agentDetails['name'],
]),
]), 200, ['Content-Type' => 'application/json']);
}
$agent = $entityManager->getRepository('UVDeskCoreFrameworkBundle:User')->findOneById($requestContent['value']);
if (empty($agent)) {
// User does not exist
return new Response(json_encode([
'alertClass' => 'danger',
'alertMessage' => $this->get('translator')->trans('Unable to retrieve agent details'),
]), 404, ['Content-Type' => 'application/json']);
} else {
// Check if an agent instance exists for the user
$agentInstance = $agent->getAgentInstance();
if (empty($agentInstance)) {
// Agent does not exist
return new Response(json_encode([
'alertClass' => 'danger',
'alertMessage' => $this->get('translator')->trans('Unable to retrieve agent details'),
]), 404, ['Content-Type' => 'application/json']);
}
}
$agentDetails = $agentInstance->getPartialDetails();
// Check if ticket is already assigned to the agent
if ($ticket->getAgent() && $agent->getId() === $ticket->getAgent()->getId()) {
return new Response(json_encode([
'alertClass' => 'success',
'alertMessage' => $this->get('translator')->trans('Ticket already assigned to %agent%', [
'%agent%' => $agentDetails['name'],
]),
]), 200, ['Content-Type' => 'application/json']);
} else {
$ticket->setAgent($agent);
$entityManager->persist($ticket);
$entityManager->flush();
// Trigger Agent Assign event
$event = new GenericEvent(CoreWorkflowEvents\Ticket\Agent::getId(), [
'entity' => $ticket,
]);
$this->get('event_dispatcher')->dispatch('uvdesk.automation.workflow.execute', $event);
return new Response(json_encode([
'alertClass' => 'success',
'alertMessage' => $this->get('translator')->trans('Ticket successfully assigned to %agent%', [
'%agent%' => $agentDetails['name'],
]),
]), 200, ['Content-Type' => 'application/json']);
}
break;
case 'status':
$ticketStatus = $entityManager->getRepository('UVDeskCoreFrameworkBundle:TicketStatus')->findOneById((int) $requestContent['value']);
if (empty($ticketStatus)) {
// Selected ticket status does not exist
return new Response(json_encode([
'alertClass' => 'danger',
'alertMessage' => $this->get('translator')->trans('Unable to retrieve status details'),
]), 404, ['Content-Type' => 'application/json']);
}
if ($ticketStatus->getId() === $ticket->getStatus()->getId()) {
return new Response(json_encode([
'alertClass' => 'success',
'alertMessage' => $this->get('translator')->trans('Ticket status already set to %status%', [
'%status%' => $ticketStatus->getDescription()
]),
]), 200, ['Content-Type' => 'application/json']);
} else {
$ticket->setStatus($ticketStatus);
$entityManager->persist($ticket);
$entityManager->flush();
// Trigger ticket status event
$event = new GenericEvent(CoreWorkflowEvents\Ticket\Status::getId(), [
'entity' => $ticket,
]);
$this->get('event_dispatcher')->dispatch('uvdesk.automation.workflow.execute', $event);
if($ticketStatus->getDescription() == 'Closed' || $ticketStatus->getDescription() == 'Closed by autoclose'){
$datosEncuesta = file_get_contents($_ENV['EXTERNAL_PATH']."UserController.php?case=5&nroTicket=".$ticket->getId()."&idUsuario=".$ticket->getCustomer()->getId());
$stringUrl = base64_encode(rand(1000,10000)."#0#".$datosEncuesta);
$stringUrl = urldecode(str_replace("=", "", $stringUrl)); //elimino los "=" porque el base64 no los necesita
$idioma = explode('#', $datosEncuesta)[3];
$link_encuesta = "https://www.softguard.com/encuesta-soporte24/".$idioma."/?p=".$stringUrl;
$mensaje = 'Hemos finalizado la asistencia solicitada recientemente.
Su opinión y comentario nos ayudará a mejorar nuestro servicio de soporte. Por favor, responda este breve cuestionario para que continuemos brindando la mejor atención que usted se merece, no tomará más de 3 minutos.
Califique nuestro Centro de Soporte';
//$apiKey = "AIzaSyBzVmrEVgd5Bb0r8bbgl3ZznsNuRGnO3RM"; // prod
$apiKey = "AIzaSyAenP2qCf3xrpbuLvzqzmsne0lJDewwP0w"; // test
$url = 'https://www.googleapis.com/language/translate/v2?key=' . $apiKey . '&q=' . rawurlencode($mensaje) . '&source=es&target='.$idioma;
$handle = curl_init($url);
curl_setopt($handle, CURLOPT_RETURNTRANSFER, true); //We want the result to be saved into variable, not printed out
$response = curl_exec($handle);
$responseDecoded = json_decode($response, true);
if($idioma == 'es'){
$mensajeFinal = $mensaje;
}else{
$mensajeFinal = $responseDecoded['data']['translations'][0]['translatedText'];
}
$invitacion_encuesta = 'Hola '.$ticket->getCustomer()->getFirstName().'<br></br><br></br>'.$mensajeFinal;
$message_id = $this->get('email.service')->sendMail('Encuesta Softguard', $invitacion_encuesta. ' : <a href="' .$link_encuesta. '">CALIFICAR LA ASISTENCIA</a> <br></br><br></br> SG Support Center ', $ticket->getCustomer()->getEmail(), []);
curl_close($handle);
}
return new Response(json_encode([
'alertClass' => 'success',
'alertMessage' => $this->get('translator')->trans('Ticket status update to %status%', [
'%status%' => $ticketStatus->getDescription()
]),
]), 200, ['Content-Type' => 'application/json']);
}
break;
case 'priority':
// $this->isAuthorized('ROLE_AGENT_UPDATE_TICKET_PRIORITY');
$ticketPriority = $entityManager->getRepository('UVDeskCoreFrameworkBundle:TicketPriority')->findOneById($requestContent['value']);
if (empty($ticketPriority)) {
// Selected ticket priority does not exist
return new Response(json_encode([
'alertClass' => 'danger',
'alertMessage' => $this->get('translator')->trans('Unable to retrieve priority details'),
]), 404, ['Content-Type' => 'application/json']);
}
if ($ticketPriority->getId() === $ticket->getPriority()->getId()) {
return new Response(json_encode([
'alertClass' => 'success',
'alertMessage' => $this->get('translator')->trans('Ticket priority already set to %priority%', [
'%priority%' => $ticketPriority->getDescription()
]),
]), 200, ['Content-Type' => 'application/json']);
} else {
$ticket->setPriority($ticketPriority);
$entityManager->persist($ticket);
$entityManager->flush();
// Trigger ticket Priority event
$event = new GenericEvent(CoreWorkflowEvents\Ticket\Priority::getId(), [
'entity' => $ticket,
]);
$this->get('event_dispatcher')->dispatch('uvdesk.automation.workflow.execute', $event);
return new Response(json_encode([
'alertClass' => 'success',
'alertMessage' => $this->get('translator')->trans('Ticket priority updated to %priority%', [
'%priority%' => $ticketPriority->getDescription()
]),
]), 200, ['Content-Type' => 'application/json']);
}
break;
case 'group':
$supportGroup = $entityManager->getRepository('UVDeskCoreFrameworkBundle:SupportGroup')->findOneById($requestContent['value']);
if (empty($supportGroup)) {
if ($requestContent['value'] == "") {
if ($ticket->getSupportGroup() != null) {
$ticket->setSupportGroup(null);
$entityManager->persist($ticket);
$entityManager->flush();
}
$responseCode = 200;
$response = [
'alertClass' => 'success',
'alertMessage' => $this->get('translator')->trans('Ticket support group updated successfully'),
];
} else {
$responseCode = 404;
$response = [
'alertClass' => 'danger',
'alertMessage' => $this->get('translator')->trans('Unable to retrieve support group details'),
];
}
return new Response(json_encode($response), $responseCode, ['Content-Type' => 'application/json']);;
}
if ($ticket->getSupportGroup() != null && $supportGroup->getId() === $ticket->getSupportGroup()->getId()) {
return new Response(json_encode([
'alertClass' => 'success',
'alertMessage' => 'Ticket already assigned to support group ' . $supportGroup->getName(),
]), 200, ['Content-Type' => 'application/json']);
} else {
$ticket->setSupportGroup($supportGroup);
$entityManager->persist($ticket);
$entityManager->flush();
// Trigger Support group event
$event = new GenericEvent(CoreWorkflowEvents\Ticket\Group::getId(), [
'entity' => $ticket,
]);
$this->get('event_dispatcher')->dispatch('uvdesk.automation.workflow.execute', $event);
return new Response(json_encode([
'alertClass' => 'success',
'alertMessage' => 'Ticket assigned to support group ' . $supportGroup->getName(),
]), 200, ['Content-Type' => 'application/json']);
}
break;
case 'team':
$supportTeam = $entityManager->getRepository('UVDeskCoreFrameworkBundle:SupportTeam')->findOneById($requestContent['value']);
if (empty($supportTeam)) {
if ($requestContent['value'] == "") {
if ($ticket->getSupportTeam() != null) {
$ticket->setSupportTeam(null);
$entityManager->persist($ticket);
$entityManager->flush();
}
$responseCode = 200;
$response = [
'alertClass' => 'success',
'alertMessage' => $this->get('translator')->trans('Ticket support team updated successfully'),
];
} else {
$responseCode = 404;
$response = [
'alertClass' => 'danger',
'alertMessage' => $this->get('translator')->trans('Unable to retrieve support team details'),
];
}
return new Response(json_encode($response), $responseCode, ['Content-Type' => 'application/json']);;
}
if ($ticket->getSupportTeam() != null && $supportTeam->getId() === $ticket->getSupportTeam()->getId()) {
return new Response(json_encode([
'alertClass' => 'success',
'alertMessage' => 'Ticket already assigned to support team ' . $supportTeam->getName(),
]), 200, ['Content-Type' => 'application/json']);
} else {
$ticket->setSupportTeam($supportTeam);
$entityManager->persist($ticket);
$entityManager->flush();
// Trigger ticket delete event
$event = new GenericEvent(CoreWorkflowEvents\Ticket\Team::getId(), [
'entity' => $ticket,
]);
$this->get('event_dispatcher')->dispatch('uvdesk.automation.workflow.execute', $event);
return new Response(json_encode([
'alertClass' => 'success',
'alertMessage' => 'Ticket assigned to support team ' . $supportTeam->getName(),
]), 200, ['Content-Type' => 'application/json']);
}
break;
case 'type':
// $this->isAuthorized('ROLE_AGENT_UPDATE_TICKET_TYPE');
$ticketType = $entityManager->getRepository('UVDeskCoreFrameworkBundle:TicketType')->findOneById($requestContent['value']);
if (empty($ticketType)) {
// Selected ticket priority does not exist
return new Response(json_encode([
'alertClass' => 'danger',
'alertMessage' => 'Unable to retrieve ticket type details',
]), 404, ['Content-Type' => 'application/json']);
}
if (null != $ticket->getType() && $ticketType->getId() === $ticket->getType()->getId()) {
return new Response(json_encode([
'alertClass' => 'success',
'alertMessage' => 'Ticket type already set to ' . $ticketType->getDescription(),
]), 200, ['Content-Type' => 'application/json']);
} else {
$ticket->setType($ticketType);
$entityManager->persist($ticket);
$entityManager->flush();
// Trigger ticket delete event
$event = new GenericEvent(CoreWorkflowEvents\Ticket\Type::getId(), [
'entity' => $ticket,
]);
$this->get('event_dispatcher')->dispatch('uvdesk.automation.workflow.execute', $event);
return new Response(json_encode([
'alertClass' => 'success',
'alertMessage' => 'Ticket type updated to ' . $ticketType->getDescription(),
]), 200, ['Content-Type' => 'application/json']);
}
break;
case 'label':
$label = $entityManager->getRepository('UVDeskCoreFrameworkBundle:SupportLabel')->find($requestContent['labelId']);
if($label) {
$ticket->removeSupportLabel($label);
$entityManager->persist($ticket);
$entityManager->flush();
return new Response(json_encode([
'alertClass' => 'success',
'alertMessage' => $this->get('translator')->trans('Success ! Ticket to label removed successfully.'),
]), 200, ['Content-Type' => 'application/json']);
}
break;
default:
break;
}
return new Response(json_encode([]), 400, ['Content-Type' => 'application/json']);
}
public function listTicketCollectionXHR(Request $request)
{
if ($request->isXmlHttpRequest()) {
$paginationResponse = $this->get('ticket.service')->paginateMembersTicketCollection($request);
return new Response(json_encode($paginationResponse), 200, ['Content-Type' => 'application/json']);
}
return new Response(json_encode([]), 404, ['Content-Type' => 'application/json']);
}
public function updateTicketCollectionXHR(Request $request)
{
if ($request->isXmlHttpRequest()) {
$massResponse = $this->get('ticket.service')->massXhrUpdate($request);
return new Response(json_encode($massResponse), 200, ['Content-Type' => 'application/json']);
}
return new Response(json_encode([]), 404);
}
public function loadTicketFilterOptionsXHR(Request $request)
{
return new Response(json_encode([]), 404);
}
public function saveTicketLabel(Request $request)
{
$entityManager = $this->getDoctrine()->getManager();
$request = $this->container->get('request_stack')->getCurrentRequest();
$requestContent = json_decode($request->getContent(), true);
$ticket = $entityManager->getRepository('UVDeskCoreFrameworkBundle:Ticket')->findOneById($requestContent['ticketId']);
if ('POST' == $request->getMethod()) {
$responseContent = [];
$user = $this->get('user.service')->getSessionUser();
$supportLabel = $entityManager->getRepository('UVDeskCoreFrameworkBundle:SupportLabel')->findOneBy([
'user' => $user->getId(),
'name' => $requestContent['name'],
]);
if (empty($supportLabel)) {
$supportLabel = new SupportLabel();
$supportLabel->setName($requestContent['name']);
$supportLabel->setUser($user);
$entityManager->persist($supportLabel);
$entityManager->flush();
}
$ticketLabelCollection = $ticket->getSupportLabels()->toArray();
if (empty($ticketLabelCollection)) {
$ticket->addSupportLabel($supportLabel);
$entityManager->persist($ticket);
$entityManager->flush();
$responseContent['alertClass'] = 'success';
$responseContent['alertMessage'] = $this->get('translator')->trans(
'Label %label% added to ticket successfully', [
'%label%' => $supportLabel->getName(),
]);
} else {
$isLabelAlreadyAdded = false;
foreach ($ticketLabelCollection as $ticketLabel) {
if ($supportLabel->getId() == $ticketLabel->getId()) {
$isLabelAlreadyAdded = true;
break;
}
}
if (false == $isLabelAlreadyAdded) {
$ticket->addSupportLabel($supportLabel);
$entityManager->persist($ticket);
$entityManager->flush();
$responseContent['alertClass'] = 'success';
$responseContent['alertMessage'] = $this->get('translator')->trans(
'Label %label% added to ticket successfully', [
'%label%' => $supportLabel->getName(),
]);
} else {
$responseContent['alertClass'] = 'warning';
$responseContent['alertMessage'] = $this->get('translator')->trans(
'Label %label% already added to ticket', [
'%label%' => $supportLabel->getName(),
]);
}
}
$responseContent['label'] = [
'id' => $supportLabel->getId(),
'name' => $supportLabel->getName(),
'color' => $supportLabel->getColorCode(),
];
return new Response(json_encode($responseContent), 200, ['Content-Type' => 'application/json']);
}
return new Response(json_encode([]), 404, ['Content-Type' => 'application/json']);
}
public function getLabels($request = null)
{
static $labels;
if (null !== $labels)
return $labels;
$qb = $this->em->createQueryBuilder();
$qb->select('tl')->from('UVDeskCoreFrameworkBundle:TicketLabel', 'tl')
->andwhere('tl.labelUser = :labelUserId')
->andwhere('tl.company = :companyId')
->setParameter('labelUserId', $this->getUser()->getId())
->setParameter('companyId', $this->getCompany()->getId());
if($request) {
$qb->andwhere("tl.name LIKE :labelName");
$qb->setParameter('labelName', '%'.urldecode($request->query->get('query')).'%');
}
return $labels = $qb->getQuery()->getArrayResult();
}
public function loadTicketSearchFilterOptions(Request $request)
{
if (true === $request->isXmlHttpRequest()) {
switch ($request->query->get('type')) {
case 'agent':
$filtersResponse = $this->get('user.service')->getAgentPartialDataCollection($request);
break;
case 'customer':
$filtersResponse = $this->get('user.service')->getCustomersPartial($request);
break;
case 'group':
$filtersResponse = $this->get('user.service')->getGroups($request);
break;
case 'team':
$filtersResponse = $this->get('user.service')->getSubGroups($request);
break;
case 'tag':
$filtersResponse = $this->get('ticket.service')->getTicketTags($request);
break;
case 'label':
$searchTerm = $request->query->get('query');
$entityManager = $this->getDoctrine()->getManager();
$supportLabelQuery = $entityManager->createQueryBuilder()->select('supportLabel')
->from('UVDeskCoreFrameworkBundle:SupportLabel', 'supportLabel')
->where('supportLabel.user = :user')->setParameter('user', $this->get('user.service')->getSessionUser());
if (!empty($searchTerm)) {
$supportLabelQuery->andWhere('supportLabel.name LIKE :labelName')->setParameter('labelName', '%' . urldecode($searchTerm) . '%');
}
$supportLabelCollection = $supportLabelQuery->getQuery()->getArrayResult();
return new Response(json_encode($supportLabelCollection), 200, ['Content-Type' => 'application/json']);
break;
default:
break;
}
}
return new Response(json_encode([]), 404, ['Content-Type' => 'application/json']);
}
public function loadTicketCollectionSearchFilterOptionsXHR(Request $request)
{
$json = [];
if ($request->isXmlHttpRequest()) {
if ($request->query->get('type') == 'agent') {
$json = $this->get('user.service')->getAgentsPartialDetails($request);
} elseif ($request->query->get('type') == 'customer') {
$json = $this->get('user.service')->getCustomersPartial($request);
} elseif ($request->query->get('type') == 'group') {
$json = $this->get('user.service')->getGroups($request);
} elseif ($request->query->get('type') == 'team') {
$json = $this->get('user.service')->getSubGroups($request);
} elseif ($request->query->get('type') == 'tag') {
$json = $this->get('ticket.service')->getTicketTags($request);
} elseif ($request->query->get('type') == 'label') {
$json = $this->get('ticket.service')->getLabels($request);
}
}
return new Response(json_encode($json), 200, ['Content-Type' => 'application/json']);
}
public function listTicketTypeCollectionXHR(Request $request)
{
if (!$this->get('user.service')->isAccessAuthorized('ROLE_AGENT_MANAGE_TICKET_TYPE')) {
return $this->redirect($this->generateUrl('helpdesk_member_dashboard'));
}
if (true === $request->isXmlHttpRequest()) {
$paginationResponse = $this->get('ticket.service')->paginateMembersTicketTypeCollection($request);
return new Response(json_encode($paginationResponse), 200, ['Content-Type' => 'application/json']);
}
return new Response(json_encode([]), 404, ['Content-Type' => 'application/json']);
}
public function removeTicketTypeXHR($typeId, Request $request)
{
if (!$this->get('user.service')->isAccessAuthorized('ROLE_AGENT_MANAGE_TICKET_TYPE')) {
return $this->redirect($this->generateUrl('helpdesk_member_dashboard'));
}
$json = [];
if($request->getMethod() == "DELETE") {
$em = $this->getDoctrine()->getManager();
$id = $request->attributes->get('typeId');
$type = $em->getRepository('UVDeskCoreFrameworkBundle:TicketType')->find($id);
// $this->get('event.manager')->trigger([
// 'event' => 'type.deleted',
// 'entity' => $type
// ]);
$em->remove($type);
$em->flush();
$json['alertClass'] = 'success';
$json['alertMessage'] = $this->get('translator')->trans('Success ! Type removed successfully.');
}
$response = new Response(json_encode($json));
$response->headers->set('Content-Type', 'application/json');
return $response;
}
public function listTagCollectionXHR(Request $request)
{
if (!$this->get('user.service')->isAccessAuthorized('ROLE_AGENT_MANAGE_TAG')) {
return $this->redirect($this->generateUrl('helpdesk_member_dashboard'));
}
if (true === $request->isXmlHttpRequest()) {
$paginationResponse = $this->get('ticket.service')->paginateMembersTagCollection($request);
return new Response(json_encode($paginationResponse), 200, ['Content-Type' => 'application/json']);
}
return new Response(json_encode([]), 404, ['Content-Type' => 'application/json']);
}
public function applyTicketPreparedResponseXHR(Request $request)
{
$id = $request->attributes->get('id');
$ticketId = $request->attributes->get('ticketId');
$ticket = $this->getDoctrine()->getManager()->getRepository('UVDeskCoreFrameworkBundle:Ticket')->findOneById($ticketId);
$event = new GenericEvent($id, [
'entity' => $ticket
]);
$this->get('event_dispatcher')->dispatch('uvdesk.automation.prepared_response.execute', $event);
$this->addFlash('success', $this->get('translator')->trans('Success ! Prepared Response applied successfully.'));
return $this->redirect($this->generateUrl('helpdesk_member_ticket',['ticketId' => $ticketId]));
}
public function loadTicketSavedReplies(Request $request)
{
$json = array();
$data = $request->query->all();
if ($request->isXmlHttpRequest()) {
$json['message'] = $this->get('ticket.service')->getSavedReplyContent($data['id'],$data['ticketId']);
}
$response = new Response(json_encode($json));
return $response;
}
public function createTicketTagXHR(Request $request)
{
$json = [];
$content = json_decode($request->getContent(), true);
$em = $this->getDoctrine()->getManager();
$ticket = $em->getRepository('UVDeskCoreFrameworkBundle:Ticket')->find($content['ticketId']);
if($request->getMethod() == "POST") {
$tag = new CoreFrameworkBundleEntities\Tag();
if ($content['name'] != "") {
$checkTag = $em->getRepository('UVDeskCoreFrameworkBundle:Tag')->findOneBy(array('name' => $content['name']));
if(!$checkTag) {
$tag->setName($content['name']);
$em->persist($tag);
$em->flush();
//$json['tag'] = json_decode($this->objectSerializer($tag));
$ticket->addSupportTag($tag);
} else {
//$json['tag'] = json_decode($this->objectSerializer($checkTag));
$ticket->addSupportTag($checkTag);
}
$em->persist($ticket);
$em->flush();
$json['alertClass'] = 'success';
$json['alertMessage'] = $this->get('translator')->trans('Success ! Tag added successfully.');
} else {
$json['alertClass'] = 'danger';
$json['alertMessage'] = $this->get('translator')->trans('Please enter tag name.');
}
} elseif($request->getMethod() == "DELETE") {
$tag = $em->getRepository('UVDeskCoreFrameworkBundle:Tag')->findOneBy(array('id' => $request->attributes->get('id')));
if($tag) {
$articles = $em->getRepository('UVDeskSupportCenterBundle:ArticleTags')->findOneBy(array('tagId' => $tag->getId()));
if($articles)
foreach ($articles as $entry) {
$em->remove($entry);
}
$ticket->removeSupportTag($tag);
$em->persist($ticket);
$em->flush();
$json['alertClass'] = 'success';
$json['alertMessage'] = $this->get('translator')->trans('Success ! Tag unassigned successfully.');
} else {
$json['alertClass'] = 'danger';
$json['alertMessage'] = $this->get('translator')->trans('Error ! Invalid tag.');
}
}
$response = new Response(json_encode($json));
$response->headers->set('Content-Type', 'application/json');
return $response;
}
public function getSearchFilterOptionsXhr(Request $request)
{
$json = [];
if ($request->isXmlHttpRequest()) {
if($request->query->get('type') == 'agent') {
$json = $this->get('user.service')->getAgentsPartialDetails($request);
} elseif($request->query->get('type') == 'customer') {
$json = $this->get('user.service')->getCustomersPartial($request);
} elseif($request->query->get('type') == 'group') {
$json = $this->get('user.service')->getSupportGroups($request);
} elseif($request->query->get('type') == 'team') {
$json = $this->get('user.service')->getSupportTeams($request);
} elseif($request->query->get('type') == 'tag') {
$json = $this->get('ticket.service')->getTicketTags($request);
} elseif($request->query->get('type') == 'label') {
$json = $this->get('ticket.service')->getLabels($request);
}
}
$response = new Response(json_encode($json));
$response->headers->set('Content-Type', 'application/json');
return $response;
}
public function updateCollaboratorXHR(Request $request)
{
$json = [];
$content = json_decode($request->getContent(), true);
$em = $this->getDoctrine()->getManager();
$ticket = $em->getRepository('UVDeskCoreFrameworkBundle:Ticket')->find($content['ticketId']);
if($request->getMethod() == "POST") {
if($content['email'] == $ticket->getCustomer()->getEmail()) {
$json['alertClass'] = 'danger';
$json['alertMessage'] = $this->get('translator')->trans('Error ! Customer can not be added as collaborator.');
} else {
$data = array(
'from' => $content['email'],
'firstName' => ($firstName = ucfirst(current(explode('@', $content['email'])))),
'lastName' => ' ',
'role' => 4,
);
$supportRole = $em->getRepository('UVDeskCoreFrameworkBundle:SupportRole')->findOneByCode('ROLE_CUSTOMER');
$collaborator = $this->get('user.service')->createUserInstance($data['from'], $data['firstName'], $supportRole, $extras = ["active" => true]);
$checkTicket = $em->getRepository('UVDeskCoreFrameworkBundle:Ticket')->isTicketCollaborator($ticket, $content['email']);
if (!$checkTicket) {
$ticket->addCollaborator($collaborator);
$em->persist($ticket);
$em->flush();
$ticket->lastCollaborator = $collaborator;
if ($collaborator->getCustomerInstance())
$json['collaborator'] = $collaborator->getCustomerInstance()->getPartialDetails();
else
$json['collaborator'] = $collaborator->getAgentInstance()->getPartialDetails();
$event = new GenericEvent(CoreWorkflowEvents\Ticket\Collaborator::getId(), [
'entity' => $ticket,
]);
$this->get('event_dispatcher')->dispatch('uvdesk.automation.workflow.execute', $event);
$json['alertClass'] = 'success';
$json['alertMessage'] = $this->get('translator')->trans('Success ! Collaborator added successfully.');
} else {
$json['alertClass'] = 'danger';
$message = "Collaborator is already added.";
$json['alertMessage'] = $this->get('translator')->trans('Error ! ' . $message);
}
}
} elseif($request->getMethod() == "DELETE") {
$collaborator = $em->getRepository('UVDeskCoreFrameworkBundle:User')->findOneBy(array('id' => $request->attributes->get('id')));
if($collaborator) {
$ticket->removeCollaborator($collaborator);
$em->persist($ticket);
$em->flush();
$json['alertClass'] = 'success';
$json['alertMessage'] = $this->get('translator')->trans('Success ! Collaborator removed successfully.');
} else {
$json['alertClass'] = 'danger';
$json['alertMessage'] = $this->get('translator')->trans('Error ! Invalid Collaborator.');
}
}
$response = new Response(json_encode($json));
$response->headers->set('Content-Type', 'application/json');
return $response;
}
// Apply quick Response action
public function getTicketQuickViewDetailsXhr(Request $request)
{
$json = [];
if ($request->isXmlHttpRequest()) {
$ticketId = $request->query->get('ticketId');
$json = $this->getDoctrine()->getRepository('UVDeskCoreFrameworkBundle:Ticket')->getTicketDetails($request->query,$this->container);
}
$response = new Response(json_encode($json));
$response->headers->set('Content-Type', 'application/json');
return $response;
}
public function updateTicketTagXHR(Request $request, $tagId)
{
$content = json_decode($request->getContent(), true);
$entityManager = $this->getDoctrine()->getManager();
if (isset($content['name']) && $content['name'] != "") {
$checkTag = $entityManager->getRepository('UVDeskCoreFrameworkBundle:Tag')->findOneBy(array('id' => $tagId));
if($checkTag) {
$checkTag->setName($content['name']);
$entityManager->persist($checkTag);
$entityManager->flush();
}
$json['alertClass'] = 'success';
$json['alertMessage'] = $this->get('translator')->trans('Success ! Tag updated successfully.');
}
$response = new Response(json_encode($json));
$response->headers->set('Content-Type', 'application/json');
return $response;
}
public function removeTicketTagXHR($tagId)
{
$entityManager = $this->getDoctrine()->getManager();
$checkTag = $entityManager->getRepository('UVDeskCoreFrameworkBundle:Tag')->findOneBy(array('id' => $tagId));
if($checkTag) {
$entityManager->remove($checkTag);
$entityManager->flush();
$json['alertClass'] = 'success';
$json['alertMessage'] = $this->get('translator')->trans('Success ! Tag removed successfully.');
}
$response = new Response(json_encode($json));
$response->headers->set('Content-Type', 'application/json');
return $response;
}
/**
* Get overdue tickets grouped by status for the current agent (solo Pending y Resolved)
*/
public function getAgentTicketAlertsXHR(Request $request)
{
$entityManager = $this->getDoctrine()->getManager();
$activeUser = $this->container->get('user.service')->getSessionUser();
if (!$activeUser) {
return new Response(json_encode(['overdue' => []]), 200, ['Content-Type' => 'application/json']);
}
$threeDaysAgo = new \DateTime();
$threeDaysAgo->modify('-3 days');
$qb = $entityManager->createQueryBuilder();
$qb->select('status.description AS statusName, COUNT(t.id) AS count')
->from('UVDeskCoreFrameworkBundle:Ticket', 't')
->leftJoin('t.status', 'status')
->andWhere('t.agent = :agentId')
->andWhere('t.updatedAt < :threeDaysAgo')
->andWhere('status.id IN (:statusIds)')
->andWhere('t.isTrashed = :isTrashed')
->groupBy('status.description')
->setParameter('agentId', $activeUser->getId())
->setParameter('threeDaysAgo', $threeDaysAgo)
->setParameter('statusIds', [2, 4])
->setParameter('isTrashed', false);
$results = $qb->getQuery()->getResult();
$overdue = [];
foreach ($results as $row) {
$overdue[$row['statusName']] = (int)$row['count'];
}
$response = new Response(json_encode(['overdue' => $overdue]));
$response->headers->set('Content-Type', 'application/json');
return $response;
}
private function isKnowledgebaseActive()
{
$entityManager = $this->getDoctrine()->getManager();
$website = $entityManager->getRepository('UVDeskCoreFrameworkBundle:Website')->findOneByCode('knowledgebase');
if (!empty($website)) {
$knowledgebaseWebsite = $entityManager->getRepository('UVDeskSupportCenterBundle:KnowledgebaseWebsite')->findOneBy(['website' => $website->getId(), 'status' => true]);
if (!empty($knowledgebaseWebsite) && true == $knowledgebaseWebsite->getIsActive()) {
return true;
}
}
throw new \Symfony\Component\HttpKernel\Exception\NotFoundHttpException('Page Not Found');
}
/**
* @Route("/api/knowledgebase/documents", name="helpdesk_member_knowledgebase_documents_api", methods={"GET", "OPTIONS"})
*/
public function getKnowledgebaseDocuments(Request $request)
{
$this->isKnowledgebaseActive();
$entityManager = $this->getDoctrine()->getManager();
$articleRepository = $entityManager->getRepository('UVDeskSupportCenterBundle:Article');
// Obtener todos los artículos activos
$query = $entityManager->createQueryBuilder()
->select('a')
->from('UVDeskSupportCenterBundle:Article', 'a')
->where('a.status = :status')
->setParameter('status', 1);
// Ejecutar la consulta para obtener todos los artículos
$articles = $query->getQuery()->getResult();
// Formatear los resultados
$documents = [];
$searchQuery = $request->query->get('search');
$searchPerformed = !empty($searchQuery);
foreach ($articles as $article) {
// Obtener el contenido del artículo
$content = $article->getContent();
// Inicializar variables
$documentName = 'TEC' . $article->getId() . ' - ' . $article->getName();
$documentUrl = null;
$linkText = '';
$includeDocument = true;
// Información de depuración
$contentDebug = [
'articleId' => $article->getId(),
'hasContent' => !empty($content),
'contentLength' => !empty($content) ? strlen($content) : 0,
];
// Buscar enlaces .pdf en el contenido
if (!empty($content)) {
// Vista previa del contenido
$contentPreview = substr($content, 0, 300) . (strlen($content) > 300 ? '...' : '');
$contentDebug['contentPreview'] = $contentPreview;
// Contar menciones de PDF y enlaces
$contentDebug['pdfMentions'] = substr_count(strtolower($content), '.pdf');
$contentDebug['linkMentions'] = substr_count(strtolower($content), 'href=');
// Buscar enlaces dentro de etiquetas <a href>
preg_match_all('/<a\s+[^>]*href=["\']([^"\']+)["\'][^>]*>([^<]+)<\/a>/i', $content, $matches);
if (!empty($matches[1])) {
// Filtrar solo los enlaces relevantes (PDF o softguardtv.com o otros recursos)
$relevantLinks = [];
$relevantTexts = [];
foreach ($matches[1] as $index => $link) {
// Aceptar si es un PDF o contiene softguardtv.com o knowledge
if (preg_match('/\.pdf$/i', $link) ||
strpos($link, 'softguardtv.com') !== false ||
strpos($link, 'knowledge') !== false ||
strpos($link, 'courses') !== false ||
strpos($link, 'lectures') !== false) {
$relevantLinks[] = $link;
$relevantTexts[] = $matches[2][$index] ?? '';
}
}
// Guardar enlaces y textos relevantes
if (!empty($relevantLinks)) {
$contentDebug['foundLinks'] = $relevantLinks;
$contentDebug['linkTexts'] = $relevantTexts;
// Usar el primer enlace encontrado
$documentUrl = $relevantLinks[0];
$linkText = !empty($relevantTexts[0]) ? trim($relevantTexts[0]) : '';
// Usar texto del enlace como nombre
if (!empty($linkText)) {
$documentName = $linkText;
}
}
} else {
// Búsqueda alternativa para URLs
preg_match_all('/(https?:\/\/[^\s"\'<>]+)/i', $content, $altMatches);
if (!empty($altMatches[0])) {
// Filtrar enlaces relevantes
$relevantLinks = [];
foreach ($altMatches[0] as $link) {
if (preg_match('/\.pdf$/i', $link) ||
strpos($link, 'softguardtv.com') !== false ||
strpos($link, 'knowledge') !== false ||
strpos($link, 'courses') !== false ||
strpos($link, 'lectures') !== false) {
$relevantLinks[] = $link;
}
}
if (!empty($relevantLinks)) {
$contentDebug['foundLinks'] = $relevantLinks;
$documentUrl = $relevantLinks[0];
}
}
}
// Si se está realizando una búsqueda, verificar si el término está en el contenido
if ($searchPerformed) {
// Buscar en el contenido completo (incluye texto y enlaces)
$includeDocument = (
stripos($content, $searchQuery) !== false ||
stripos($documentName, $searchQuery) !== false ||
(!empty($documentUrl) && stripos($documentUrl, $searchQuery) !== false) ||
(!empty($linkText) && stripos($linkText, $searchQuery) !== false)
);
$contentDebug['searchPerformed'] = true;
$contentDebug['searchTerm'] = $searchQuery;
$contentDebug['matchFound'] = $includeDocument;
}
}
// Si no se encontró URL, usar formato estándar
if (empty($documentUrl)) {
$baseDocumentName = $article->getName();
$documentUrl = 'https://softguard.com/knowledge/TEC' . $article->getId() . '_' . $baseDocumentName . '.pdf';
$contentDebug['usingFallbackUrl'] = true;
}
// Solo incluir el documento si pasa los filtros de búsqueda
if ($includeDocument) {
$documents[] = [
'id' => $article->getId(),
'name' => $documentName,
'url' => $documentUrl,
'views' => $article->getViewed(),
'dateAdded' => $article->getDateAdded() ? $article->getDateAdded()->format('Y-m-d H:i:s') : null,
'contentDebug' => $contentDebug
];
}
}
// Ordenar por popularidad (vistas)
usort($documents, function($a, $b) {
return $b['views'] - $a['views'];
});
// Devolver respuesta JSON
return new JsonResponse([
'success' => true,
'documents' => $documents,
'totalFound' => count($documents),
'searchQuery' => $searchQuery
]);
}
public function getTicketSuggestions(Request $request)
{
$ticketId = $request->query->get('ticketId');
if (empty($ticketId)) {
return new JsonResponse([
'success' => false,
'message' => 'Se requiere ticketId'
], 400);
}
$entityManager = $this->getDoctrine()->getManager();
// Obtener el mensaje inicial del ticket actual
$currentTicket = $entityManager->getRepository('UVDeskCoreFrameworkBundle:Ticket')->find($ticketId);
if (!$currentTicket) {
return new JsonResponse([
'success' => false,
'message' => 'Ticket no encontrado'
], 404);
}
$initialThread = $entityManager->createQueryBuilder()
->select('t')
->from('UVDeskCoreFrameworkBundle:Thread', 't')
->where('t.ticket = :ticketId')
->andWhere('t.threadType = :threadType')
->andWhere('t.createdBy = :createdBy')
->setParameter('ticketId', $ticketId)
->setParameter('threadType', 'create')
->setParameter('createdBy', 'customer')
->orderBy('t.id', 'ASC')
->setMaxResults(1)
->getQuery()
->getOneOrNullResult();
if (!$initialThread) {
return new JsonResponse([
'success' => false,
'message' => 'No se encontró el mensaje inicial del cliente'
], 404);
}
$currentProblem = strip_tags($initialThread->getMessage());
$currentSubject = $currentTicket->getSubject();
// Extraer palabras dinámicas importantes
$dynamicImportantWords = $this->extractDynamicImportantWords($currentProblem, $currentSubject);
// Buscar tickets similares
$similarTickets = $this->findSimilarTickets($currentProblem, $currentSubject, $ticketId, $entityManager);
return new JsonResponse([
'success' => true,
'currentProblem' => $currentProblem,
'currentSubject' => $currentSubject,
'suggestions' => $similarTickets,
'totalFound' => count($similarTickets),
'debug' => [
'dynamicImportantWords' => $dynamicImportantWords,
'problemKeywords' => $this->extractKeywords($this->normalizeText($currentProblem)),
'subjectKeywords' => $this->extractKeywords($this->normalizeText($currentSubject)),
'totalTicketsSearched' => 500,
'ticketsWithReplies' => count($similarTickets)
]
]);
}
private function findSimilarTickets($currentProblem, $currentSubject, $excludeTicketId, $entityManager)
{
$normalizedProblem = $this->normalizeText($currentProblem);
$normalizedSubject = $this->normalizeText($currentSubject);
$problemKeywords = $this->extractKeywords($normalizedProblem);
$subjectKeywords = $this->extractKeywords($normalizedSubject);
// Extraer palabras dinámicas importantes del ticket actual
$dynamicImportantWords = $this->extractDynamicImportantWords($currentProblem, $currentSubject);
$query = $entityManager->createQueryBuilder()
->select('t, th, u')
->from('UVDeskCoreFrameworkBundle:Ticket', 't')
->join('t.threads', 'th')
->join('th.user', 'u')
->where('t.id != :excludeId')
->andWhere('t.status IN (:statuses)')
->andWhere('th.threadType = :threadType')
->andWhere('th.createdBy = :createdBy')
->setParameter('excludeId', $excludeTicketId)
->setParameter('statuses', [3, 4, 5])
->setParameter('threadType', 'create')
->setParameter('createdBy', 'customer')
->orderBy('t.createdAt', 'DESC')
->setMaxResults(500) // Aumentado de 200 a 500
->getQuery();
$tickets = $query->getResult();
$similarTickets = [];
foreach ($tickets as $ticket) {
$initialThread = null;
foreach ($ticket->getThreads() as $thread) {
if ($thread->getThreadType() === 'create' && $thread->getCreatedBy() === 'customer') {
$initialThread = $thread;
break;
}
}
if ($initialThread) {
$ticketProblem = strip_tags($initialThread->getMessage());
$ticketSubject = $ticket->getSubject();
$similarity = $this->calculateIntelligentSimilarity(
$normalizedProblem,
$normalizedSubject,
$this->normalizeText($ticketProblem),
$this->normalizeText($ticketSubject),
$problemKeywords,
$subjectKeywords,
$dynamicImportantWords
);
if ($similarity['total'] >= 15) { // Reducido de 20 a 15
$agentReplies = $entityManager->createQueryBuilder()
->select('th, u')
->from('UVDeskCoreFrameworkBundle:Thread', 'th')
->join('th.user', 'u')
->where('th.ticket = :ticketId')
->andWhere('th.threadType = :threadType')
->andWhere('th.createdBy = :createdBy')
->andWhere('LENGTH(th.message) > 20')
->setParameter('ticketId', $ticket->getId())
->setParameter('threadType', 'reply')
->setParameter('createdBy', 'agent')
->orderBy('th.createdAt', 'ASC')
->getQuery()
->getResult();
$solutions = [];
foreach ($agentReplies as $reply) {
$solutions[] = [
'id' => $reply->getId(),
'text' => strip_tags($reply->getMessage()),
'agent' => $reply->getUser() ? $reply->getUser()->getFirstName() . ' ' . $reply->getUser()->getLastName() : 'Agente',
'date' => $reply->getCreatedAt()->format('Y-m-d H:i:s'),
'length' => strlen(strip_tags($reply->getMessage()))
];
}
if (!empty($solutions)) {
$similarTickets[] = [
'ticketId' => $ticket->getId(),
'subject' => $ticketSubject,
'problem' => $ticketProblem,
'similarity' => $similarity,
'solutions' => $solutions,
'status' => $ticket->getStatus()->getDescription(),
'createdAt' => $ticket->getCreatedAt()->format('Y-m-d H:i:s'),
'customer' => $initialThread->getUser() ? $initialThread->getUser()->getFirstName() . ' ' . $initialThread->getUser()->getLastName() : 'Cliente'
];
}
}
}
}
usort($similarTickets, function($a, $b) {
return $b['similarity']['total'] <=> $a['similarity']['total'];
});
return array_slice($similarTickets, 0, 8);
}
private function normalizeText($text)
{
$text = strtolower($text);
$text = preg_replace('/[^\w\s]/', ' ', $text);
$text = preg_replace('/\s+/', ' ', $text);
return trim($text);
}
private function extractKeywords($text)
{
$stopWords = [
'el', 'la', 'de', 'que', 'y', 'a', 'en', 'un', 'es', 'se', 'no', 'te', 'lo', 'le', 'da', 'su', 'por', 'son', 'con', 'para', 'al', 'del', 'los', 'las', 'una', 'como', 'pero', 'sus', 'me', 'hasta', 'hay', 'donde', 'han', 'quien', 'estan', 'estado', 'desde', 'todo', 'nos', 'durante', 'todos', 'uno', 'les', 'ni', 'contra', 'otros', 'ese', 'eso', 'ante', 'ellos', 'e', 'esto', 'mi', 'antes', 'algunos', 'que', 'unos', 'yo', 'otro', 'otras', 'otra', 'el', 'tanto', 'esa', 'estos', 'mucho', 'quienes', 'nada', 'muchos', 'cual', 'poco', 'ella', 'estar', 'estas', 'algunas', 'algo', 'nosotros', 'este', 'esta', 'estos', 'estas', 'ese', 'esa', 'esos', 'esas', 'aquel', 'aquella', 'aquellos', 'aquellas', 'mi', 'tu', 'su', 'nuestro', 'vuestro', 'su', 'mio', 'tuyo', 'suyo', 'nuestro', 'vuestro', 'suyo', 'mios', 'tuyos', 'suyos', 'nuestros', 'vuestros', 'suyos', 'mia', 'tuya', 'suya', 'nuestra', 'vuestra', 'suya', 'mias', 'tuyas', 'suyas', 'nuestras', 'vuestras', 'suyas'
];
$words = explode(' ', $text);
$keywords = [];
foreach ($words as $word) {
$word = trim($word);
if (strlen($word) > 2 && !in_array($word, $stopWords)) {
$keywords[] = $word;
}
}
$wordCount = array_count_values($keywords);
arsort($wordCount);
return array_slice(array_keys($wordCount), 0, 15);
}
private function calculateIntelligentSimilarity($problem1, $subject1, $problem2, $subject2, $keywords1, $keywords2, $dynamicImportantWords = [])
{
$scores = [];
// 1. Similitud de texto del problema (peso reducido)
similar_text($problem1, $problem2, $problemSimilarity);
$scores['problem_text'] = $problemSimilarity;
// 2. Similitud de subject (peso aumentado significativamente)
similar_text($subject1, $subject2, $subjectSimilarity);
$scores['subject_text'] = $subjectSimilarity;
// 3. Coincidencia exacta de palabras clave (nuevo - peso alto)
$subjectKeywords1 = $this->extractKeywords($subject1);
$subjectKeywords2 = $this->extractKeywords($subject2);
$problemKeywords1 = $this->extractKeywords($problem1);
$problemKeywords2 = $this->extractKeywords($problem2);
// Combinar keywords del subject y problema para el ticket actual
$currentKeywords = array_merge($subjectKeywords1, $problemKeywords1);
$currentKeywords = array_unique($currentKeywords);
// Combinar keywords del subject y problema para el ticket comparado
$compareKeywords = array_merge($subjectKeywords2, $problemKeywords2);
$compareKeywords = array_unique($compareKeywords);
// Calcular coincidencias exactas
$exactMatches = array_intersect($currentKeywords, $compareKeywords);
$exactMatchScore = count($exactMatches) > 0 ? (count($exactMatches) / max(count($currentKeywords), count($compareKeywords))) * 100 : 0;
$scores['exact_keywords'] = $exactMatchScore;
// 4. Coincidencia de keywords general (peso reducido)
$keywordMatches = array_intersect($keywords1, $keywords2);
$keywordScore = count($keywordMatches) / max(count($keywords1), count($keywords2)) * 100;
$scores['keywords'] = $keywordScore;
// 5. Similitud por categorías
$categoryScore = $this->calculateCategorySimilarity($problem1 . ' ' . $subject1, $problem2 . ' ' . $subject2);
$scores['category'] = $categoryScore;
// 6. Similitud técnica
$technicalScore = $this->calculateTechnicalSimilarity($problem1 . ' ' . $subject1, $problem2 . ' ' . $subject2);
$scores['technical'] = $technicalScore;
// 7. Búsqueda de palabras específicas importantes (nuevo)
$specificWordsScore = $this->calculateSpecificWordsSimilarity($problem1 . ' ' . $subject1, $problem2 . ' ' . $subject2);
$scores['specific_words'] = $specificWordsScore;
// 8. Coincidencia de palabras dinámicas importantes (nuevo - peso muy alto)
$dynamicWordsScore = $this->calculateDynamicWordsSimilarity($problem2 . ' ' . $subject2, $dynamicImportantWords);
$scores['dynamic_words'] = $dynamicWordsScore;
// Nueva fórmula de peso que prioriza subject, palabras exactas y palabras dinámicas
$total = (
$scores['problem_text'] * 0.15 + // Reducido de 0.20
$scores['subject_text'] * 0.25 + // Reducido de 0.30
$scores['exact_keywords'] * 0.20 + // Reducido de 0.25
$scores['keywords'] * 0.10 + // Mantenido
$scores['category'] * 0.10 + // Mantenido
$scores['technical'] * 0.05 + // Mantenido
$scores['specific_words'] * 0.05 + // Reducido de 0.05
$scores['dynamic_words'] * 0.10 // Nuevo - peso alto para palabras dinámicas
);
$scores['total'] = round($total, 2);
return $scores;
}
/**
* Extrae palabras dinámicas importantes del ticket actual
*/
private function extractDynamicImportantWords($problem, $subject)
{
$importantWords = [];
// Combinar problema y subject
$combinedText = $problem . ' ' . $subject;
$normalizedText = $this->normalizeText($combinedText);
// Extraer todas las palabras
$words = explode(' ', $normalizedText);
$wordCount = array_count_values($words);
// Filtrar palabras por longitud y frecuencia
$filteredWords = [];
foreach ($wordCount as $word => $count) {
$word = trim($word);
// Incluir palabras de 3+ caracteres que aparecen al menos 1 vez
if (strlen($word) >= 3 && $count >= 1) {
$filteredWords[$word] = $count;
}
}
// Ordenar por frecuencia
arsort($filteredWords);
// Tomar las primeras 10 palabras más frecuentes
$topWords = array_slice(array_keys($filteredWords), 0, 10);
// Agregar palabras específicas que siempre son importantes
$specificImportantWords = [
'comando', 'comandos', 'programar', 'programacion', 'programa', 'programas',
'script', 'scripts', 'codigo', 'codigos', 'funcion', 'funciones',
'error', 'errores', 'falla', 'fallas', 'problema', 'problemas',
'conexion', 'conectar', 'internet', 'red', 'wifi', 'network',
'usuario', 'usuarios', 'password', 'contraseña', 'login', 'acceso',
'datos', 'archivo', 'archivos', 'documento', 'documentos',
'pago', 'pagos', 'factura', 'facturas', 'cobro', 'precio',
'soporte', 'ayuda', 'asistencia', 'tecnico', 'servicio',
'configuracion', 'ajuste', 'ajustes', 'parametro', 'parametros',
'actualizacion', 'update', 'version', 'nuevo', 'nueva', 'cambio'
];
// Buscar palabras específicas en el texto actual
foreach ($specificImportantWords as $word) {
if (stripos($combinedText, $word) !== false) {
$importantWords[] = $word;
}
}
// Agregar las palabras más frecuentes del texto actual
$importantWords = array_merge($importantWords, $topWords);
// Eliminar duplicados y retornar
return array_unique($importantWords);
}
/**
* Calcula la similitud basada en palabras dinámicas importantes
*/
private function calculateDynamicWordsSimilarity($compareText, $dynamicImportantWords)
{
if (empty($dynamicImportantWords)) {
return 0;
}
$normalizedCompareText = $this->normalizeText($compareText);
$foundWords = [];
// Buscar cada palabra importante en el texto a comparar
foreach ($dynamicImportantWords as $word) {
if (stripos($normalizedCompareText, $word) !== false) {
$foundWords[] = $word;
}
}
// Calcular score basado en la proporción de palabras encontradas
$matchRatio = count($foundWords) / count($dynamicImportantWords);
// Dar bonus extra por coincidencias múltiples
$bonus = count($foundWords) * 15;
$score = ($matchRatio * 100) + $bonus;
return min(100, $score);
}
private function calculateCategorySimilarity($text1, $text2)
{
$categories = [
'error' => ['error', 'falla', 'problema', 'bug', 'crash', 'no funciona', 'no anda'],
'conexion' => ['conexion', 'conectar', 'internet', 'red', 'wifi', 'network', 'online'],
'usuario' => ['usuario', 'password', 'contraseña', 'login', 'acceso', 'autenticacion'],
'datos' => ['datos', 'archivo', 'documento', 'informacion', 'base de datos', 'backup'],
'pago' => ['pago', 'factura', 'cobro', 'precio', 'costo', 'tarifa', 'billing'],
'soporte' => ['soporte', 'ayuda', 'asistencia', 'tecnico', 'servicio'],
'configuracion' => ['configuracion', 'ajuste', 'parametro', 'setting', 'setup'],
'actualizacion' => ['actualizacion', 'update', 'version', 'nuevo', 'cambio']
];
$score = 0;
$totalCategories = count($categories);
foreach ($categories as $category => $keywords) {
$text1HasCategory = false;
$text2HasCategory = false;
foreach ($keywords as $keyword) {
if (stripos($text1, $keyword) !== false) {
$text1HasCategory = true;
}
if (stripos($text2, $keyword) !== false) {
$text2HasCategory = true;
}
}
if ($text1HasCategory && $text2HasCategory) {
$score += 100;
} elseif ($text1HasCategory || $text2HasCategory) {
$score += 0;
}
}
return $score / $totalCategories;
}
private function calculateTechnicalSimilarity($text1, $text2)
{
$technicalTerms = [
'api', 'database', 'server', 'client', 'protocol', 'ssl', 'https', 'http',
'json', 'xml', 'rest', 'soap', 'authentication', 'authorization', 'token',
'session', 'cookie', 'cache', 'memory', 'cpu', 'disk', 'storage', 'backup',
'firewall', 'proxy', 'vpn', 'dns', 'ip', 'domain', 'subdomain', 'ssl',
'certificate', 'encryption', 'decryption', 'hash', 'md5', 'sha', 'aes',
'mysql', 'postgresql', 'mongodb', 'redis', 'elasticsearch', 'kafka',
'docker', 'kubernetes', 'aws', 'azure', 'gcp', 'cloud', 'saas', 'paas'
];
$text1Terms = [];
$text2Terms = [];
foreach ($technicalTerms as $term) {
if (stripos($text1, $term) !== false) {
$text1Terms[] = $term;
}
if (stripos($text2, $term) !== false) {
$text2Terms[] = $term;
}
}
if (empty($text1Terms) && empty($text2Terms)) {
return 0;
}
$commonTerms = array_intersect($text1Terms, $text2Terms);
$totalTerms = array_unique(array_merge($text1Terms, $text2Terms));
return count($totalTerms) > 0 ? (count($commonTerms) / count($totalTerms)) * 100 : 0;
}
private function calculateSpecificWordsSimilarity($text1, $text2)
{
// Palabras específicas importantes que deben tener peso extra
$specificWords = [
// Comandos y programación
'comando', 'comandos', 'programar', 'programacion', 'programa', 'programas',
'script', 'scripts', 'codigo', 'codigos', 'funcion', 'funciones',
'variable', 'variables', 'parametro', 'parametros', 'configuracion',
// Errores y problemas
'error', 'errores', 'falla', 'fallas', 'problema', 'problemas',
'no funciona', 'no anda', 'no aparece', 'no puedo', 'no me deja',
// Conexión y red
'conexion', 'conectar', 'conecta', 'internet', 'red', 'wifi', 'network',
'servidor', 'servidor', 'online', 'offline', 'timeout', 'timeout',
// Usuarios y acceso
'usuario', 'usuarios', 'password', 'contraseña', 'login', 'acceso',
'autenticacion', 'autorizacion', 'sesion', 'perfil', 'cuenta',
// Datos y archivos
'datos', 'archivo', 'archivos', 'documento', 'documentos', 'informacion',
'base de datos', 'backup', 'respaldo', 'guardar', 'guardado',
// Pagos y facturación
'pago', 'pagos', 'factura', 'facturas', 'cobro', 'precio', 'costo',
'tarifa', 'billing', 'invoice', 'payment', 'charge',
// Soporte y ayuda
'soporte', 'ayuda', 'asistencia', 'tecnico', 'servicio', 'servicios',
'manual', 'guia', 'tutorial', 'instrucciones', 'pasos',
// Configuración y ajustes
'configuracion', 'ajuste', 'ajustes', 'parametro', 'parametros',
'setting', 'settings', 'setup', 'instalacion', 'instalar',
// Actualizaciones y cambios
'actualizacion', 'update', 'version', 'nuevo', 'nueva', 'cambio',
'cambios', 'mejora', 'mejoras', 'nueva version', 'actualizar'
];
$text1Words = [];
$text2Words = [];
// Buscar palabras específicas en ambos textos
foreach ($specificWords as $word) {
if (stripos($text1, $word) !== false) {
$text1Words[] = $word;
}
if (stripos($text2, $word) !== false) {
$text2Words[] = $word;
}
}
// Si no hay palabras específicas en ninguno, retornar 0
if (empty($text1Words) && empty($text2Words)) {
return 0;
}
// Calcular coincidencias
$commonWords = array_intersect($text1Words, $text2Words);
$totalWords = array_unique(array_merge($text1Words, $text2Words));
// Dar peso extra si hay coincidencias exactas
$baseScore = count($totalWords) > 0 ? (count($commonWords) / count($totalWords)) * 100 : 0;
// Bonus por coincidencias múltiples
$bonus = count($commonWords) * 10;
return min(100, $baseScore + $bonus);
}
/**
* Función auxiliar para convertir texto en un slug
*/
private function slugify($text)
{
// Reemplazar espacios con guiones bajos
$text = str_replace(' ', '_', $text);
// Eliminar caracteres especiales
$text = preg_replace('/[^A-Za-z0-9\-_]/', '', $text);
// Convertir a minúsculas
return strtolower($text);
}
/**
* Devuelve la configuración de alertas de tickets desde archivo config/ticket_alerts.json
*/
public function getTicketAlertConfigXHR(Request $request)
{
$kernel = $this->get('kernel');
$projectRoot = $kernel->getProjectDir();
$configFile = $projectRoot . '/config/ticket_alerts.json';
$default = [
'ticket_alert_interval' => 1800000,
'ticket_alert_enabled' => true,
'ticket_alert_days_threshold' => 3
];
if (file_exists($configFile)) {
$content = file_get_contents($configFile);
$params = json_decode($content, true);
$interval = isset($params['ticket_alert_interval']) ? (int)$params['ticket_alert_interval'] : $default['ticket_alert_interval'];
$enabled = isset($params['ticket_alert_enabled']) ? (bool)$params['ticket_alert_enabled'] : $default['ticket_alert_enabled'];
$daysThreshold = isset($params['ticket_alert_days_threshold']) ? (int)$params['ticket_alert_days_threshold'] : $default['ticket_alert_days_threshold'];
} else {
$interval = $default['ticket_alert_interval'];
$enabled = $default['ticket_alert_enabled'];
$daysThreshold = $default['ticket_alert_days_threshold'];
}
$config = [
'interval' => $interval,
'enabled' => $enabled,
'days_threshold' => $daysThreshold,
'message' => '¡Hay tickets vencidos!'
];
return new JsonResponse($config);
}
}