diff --git a/cp/app/Controllers/SupportController.php b/cp/app/Controllers/SupportController.php index 13ff210..02b91f6 100644 --- a/cp/app/Controllers/SupportController.php +++ b/cp/app/Controllers/SupportController.php @@ -24,7 +24,7 @@ class SupportController extends Controller $categories = $db->select("SELECT * FROM ticket_categories"); $category = $data['category'] ?? null; - $subject = $data['subject'] ?? null; + $subject = htmlspecialchars($data['subject'], ENT_QUOTES, 'UTF-8') ?? null; $message = $data['message'] ?? null; if (!$subject) { @@ -103,15 +103,22 @@ class SupportController extends Controller public function viewTicket(Request $request, Response $response, $args) { - $rawNumber = $args; - $ticketNumber = filter_var($rawNumber, FILTER_VALIDATE_INT); + $ticketNumber = filter_var($args, FILTER_VALIDATE_INT); + $db = $this->container->get('db'); if ($ticketNumber === false) { $this->container->get('flash')->addMessage('error', 'Invalid ticket number'); return $response->withHeader('Location', '/support')->withStatus(302); } + + $result = $db->selectRow('SELECT registrar_id FROM registrar_users WHERE user_id = ?', [$_SESSION['auth_user_id']]); + $clid = $_SESSION["auth_roles"] != 0 ? $result['registrar_id'] : $_SESSION['auth_user_id']; + $ticket_owner = $db->selectValue('SELECT user_id FROM support_tickets WHERE id = ?', [$ticketNumber]); + + if ($ticket_owner != $clid && $_SESSION["auth_roles"] != 0) { + return $response->withHeader('Location', '/support')->withStatus(302); + } - $db = $this->container->get('db'); // Get the current URI $uri = $request->getUri()->getPath(); @@ -142,7 +149,7 @@ class SupportController extends Controller } public function replyTicket(Request $request, Response $response) - { + { if ($request->getMethod() === 'POST') { // Retrieve POST data $data = $request->getParsedBody(); @@ -153,6 +160,15 @@ class SupportController extends Controller $ticket_id = $data['ticket_id'] ?? null; $responseText = $data['responseText'] ?? null; + + $result = $db->selectRow('SELECT registrar_id FROM registrar_users WHERE user_id = ?', [$_SESSION['auth_user_id']]); + $clid = $_SESSION["auth_roles"] != 0 ? $result['registrar_id'] : $_SESSION['auth_user_id']; + $ticket_owner = $db->selectValue('SELECT user_id FROM support_tickets WHERE id = ?', [$ticket_id]); + + if ($ticket_owner != $clid && $_SESSION["auth_roles"] != 0) { + $this->container->get('flash')->addMessage('error', 'You do not have permission to perform this action'); + return $response->withHeader('Location', '/support')->withStatus(302); + } if (!$responseText) { $this->container->get('flash')->addMessage('error', 'Please enter a reply'); @@ -172,6 +188,17 @@ class SupportController extends Controller 'date_created' => $crdate, ] ); + + $db->update( + 'support_tickets', + [ + 'status' => 'In Progress', + 'last_updated' => $crdate + ], + [ + 'id' => $ticket_id + ] + ); $link = envi('APP_URL').'/ticket/'.$ticket_id; $email = $db->selectValue('SELECT email FROM users WHERE id = ?', [$_SESSION['auth_user_id']]); @@ -190,7 +217,7 @@ class SupportController extends Controller // send message Mail::send($mailsubject, $message, $from, $to); - $this->container->get('flash')->addMessage('success', 'Reply has been created successfully on ' . $crdate); + $this->container->get('flash')->addMessage('success', 'Reply has been posted successfully on ' . $crdate); return $response->withHeader('Location', '/ticket/'.$ticket_id)->withStatus(302); } catch (Exception $e) { $this->container->get('flash')->addMessage('error', 'Database error: '.$e->getMessage()); @@ -198,6 +225,88 @@ class SupportController extends Controller } } } + + public function statusTicket(Request $request, Response $response) + { + if ($request->getMethod() === 'POST') { + // Retrieve POST data + $data = $request->getParsedBody(); + $db = $this->container->get('db'); + // Get the current URI + $uri = $request->getUri()->getPath(); + $categories = $db->select("SELECT * FROM ticket_categories"); + + $ticket_id = $data['ticket_id'] ?? null; + $action = $data['action'] ?? null; + + $result = $db->selectRow('SELECT registrar_id FROM registrar_users WHERE user_id = ?', [$_SESSION['auth_user_id']]); + $clid = $_SESSION["auth_roles"] != 0 ? $result['registrar_id'] : $_SESSION['auth_user_id']; + $ticket_owner = $db->selectValue('SELECT user_id FROM support_tickets WHERE id = ?', [$ticket_id]); + + if ($ticket_owner != $clid && $_SESSION["auth_roles"] != 0) { + $this->container->get('flash')->addMessage('error', 'You do not have permission to perform this action'); + return $response->withHeader('Location', '/support')->withStatus(302); + } + + if (!$action) { + $this->container->get('flash')->addMessage('error', 'Please select an action'); + return $response->withHeader('Location', '/ticket/'.$ticket_id)->withStatus(302); + } + + try { + $currentDateTime = new \DateTime(); + $update = $currentDateTime->format('Y-m-d H:i:s.v'); + + if ($action === 'close') { + $db->update( + 'support_tickets', + [ + 'status' => 'Closed', + 'last_updated' => $update + ], + [ + 'id' => $ticket_id + ] + ); + $this->container->get('flash')->addMessage('success', 'Ticket has been closed successfully'); + return $response->withHeader('Location', '/ticket/'.$ticket_id)->withStatus(302); + } else if ($action === 'escalate') { + $db->update( + 'support_tickets', + [ + 'priority' => 'High', + 'last_updated' => $update + ], + [ + 'id' => $ticket_id + ] + ); + $this->container->get('flash')->addMessage('success', 'Ticket has been escalated successfully'); + return $response->withHeader('Location', '/ticket/'.$ticket_id)->withStatus(302); + } else if ($action === 'reopen') { + $db->update( + 'support_tickets', + [ + 'status' => 'In Progress', + 'last_updated' => $update + ], + [ + 'id' => $ticket_id + ] + ); + $this->container->get('flash')->addMessage('success', 'Ticket has been escalated successfully'); + return $response->withHeader('Location', '/ticket/'.$ticket_id)->withStatus(302); + } else { + $this->container->get('flash')->addMessage('error', 'Incorrect action specified'); + return $response->withHeader('Location', '/ticket/'.$ticket_id)->withStatus(302); + } + + } catch (Exception $e) { + $this->container->get('flash')->addMessage('error', 'Database error: '.$e->getMessage()); + return $response->withHeader('Location', '/ticket/'.$ticket_id)->withStatus(302); + } + } + } public function docs(Request $request, Response $response) { diff --git a/cp/resources/views/admin/support/viewTicket.twig b/cp/resources/views/admin/support/viewTicket.twig index 1620cd8..0b3299a 100644 --- a/cp/resources/views/admin/support/viewTicket.twig +++ b/cp/resources/views/admin/support/viewTicket.twig @@ -25,33 +25,65 @@
{% include 'partials/flash.twig' %} -
+ {{ csrf.field | raw }}
-
{{ __('Ticket') }} #{{ ticket.id }} - {{ ticket.subject }}
  +

{{ __('Ticket') }} #{{ ticket.id }} - {{ ticket.subject }}

  {% if ticket.status == 'Open' %} - {{ ticket.status }} + {{ ticket.status }} {% elseif ticket.status == 'In Progress' %} - {{ ticket.status }} + {{ ticket.status }} {% elseif ticket.status == 'Resolved' %} - {{ ticket.status }} + {{ ticket.status }} {% elseif ticket.status == 'Closed' %} - {{ ticket.status }} + {{ ticket.status }} {% else %} - {{ __('Unknown Status') }} + {{ __('Unknown Status') }} {% endif %} +
+ {% if ticket.status != 'Closed' %} + + + {% else %} + + {% endif %} +
+ + {% if ticket.status != 'Closed' %}
+ {{ csrf.field | raw }} + {% endif %}
{{ __('Ticket Details') }}

{{ __('Created On') }}: {{ ticket.date_created }}

-

{{ __('Category') }}: {{ category }}

-

{{ __('Priority') }}: {{ ticket.priority }}

+

{{ __('Category') }}: {{ category }}

+

{{ __('Priority') }}: + {% if ticket.priority == 'Low' %} + {{ ticket.priority }} + {% elseif ticket.priority == 'Medium' %} + {{ ticket.priority }} + {% elseif ticket.priority == 'High' %} + {{ ticket.priority }} + {% elseif ticket.priority == 'Critical' %} + {{ ticket.priority }} + {% else %} + {{ ticket.priority }} + {% endif %}

-
+
-
{{ __('Conversation') }}
+
{{ __('Conversation') }}
{% for reply in replies %}
@@ -64,7 +96,7 @@
{% endfor %} -
+
{{ ticket.ticket_creator|slice(0, 2) }}
@@ -76,11 +108,14 @@
+ {% if ticket.status != 'Closed' %}
+ {% endif %}
+ {% if ticket.status != 'Closed' %} + {% endif %}
- + {% if ticket.status != 'Closed' %}{% endif %}
diff --git a/cp/resources/views/partials/js-support.twig b/cp/resources/views/partials/js-support.twig index c627f91..d71ae96 100644 --- a/cp/resources/views/partials/js-support.twig +++ b/cp/resources/views/partials/js-support.twig @@ -28,13 +28,13 @@ switch (status) { case 'Open': - return createBadge(status, 'primary'); + return createBadge(status, 'success'); case 'In Progress': return createBadge(status, 'warning'); case 'Resolved': - return createBadge(status, 'success'); + return createBadge(status, 'info'); case 'Closed': - return createBadge(status, 'secondary'); + return createBadge(status, 'cyan'); default: return ""; } @@ -45,22 +45,33 @@ // Function to create an outline badge with color based on priority function createBadge(text, badgeClass) { - return `${text}`; + return `${text}`; } switch (priority) { case 'Low': - return createBadge(priority, 'info'); + return createBadge(priority, 'teal'); case 'Medium': - return createBadge(priority, 'secondary'); + return createBadge(priority, 'blue'); case 'High': - return createBadge(priority, 'warning'); + return createBadge(priority, 'orange'); case 'Critical': - return createBadge(priority, 'danger'); + return createBadge(priority, 'red'); default: return ""; } } + + function catFormatter(cell) { + var category = cell.getValue(); + + // Function to create an outline badge with color based on category + function createBadge(text, badgeClass) { + return `${text}`; + } + + return createBadge(category, 'indigo'); + } table = new Tabulator("#supportTable", { ajaxURL:"/api/records/support_tickets?join=ticket_categories", // Set the URL for your JSON data @@ -74,10 +85,13 @@ responsiveLayout: "collapse", responsiveLayoutCollapseStartOpen:false, resizableColumns:false, + initialSort:[ + {column:"status", dir:"desc"}, + ], columns:[ {formatter:"responsiveCollapse", width:30, minWidth:30, hozAlign:"center", resizable:false, headerSort:false, responsive:0}, {title:"{{ __('Subject') }}", field:"subject", width:350, minWidth:100, headerSort:true, formatter: ticketLinkFormatter, responsive:0}, - {title:"{{ __('Category') }}", field:"category_id.name", width:250, minWidth:80, headerSort:true, responsive:0}, + {title:"{{ __('Category') }}", field:"category_id.name", width:250, minWidth:80, formatter: catFormatter, headerSort:true, responsive:0}, {title:"{{ __('Status') }}", field:"status", headerSort:true, width:250, minWidth:100, formatter: statusFormatter, responsive:2}, {title:"{{ __('Priority') }}", field:"priority", headerSort:true, width:250, minWidth:100, formatter: priorityFormatter, responsive:2}, {title: "{{ __('Actions') }}", formatter: actionsFormatter, headerSort: false, download:false, hozAlign: "center", responsive:0, cellClick:function(e, cell){ e.stopPropagation(); }}, diff --git a/cp/routes/web.php b/cp/routes/web.php index 107b7b9..b5f732f 100644 --- a/cp/routes/web.php +++ b/cp/routes/web.php @@ -121,6 +121,7 @@ $app->group('', function ($route) { $route->map(['GET', 'POST'], '/support/new', SupportController::class .':newticket')->setName('newticket'); $route->get('/ticket/{ticket}', SupportController::class . ':viewTicket')->setName('viewTicket'); $route->post('/support/reply', SupportController::class . ':replyTicket')->setName('replyTicket'); + $route->post('/support/status', SupportController::class . ':statusTicket')->setName('statusTicket'); $route->get('/support/docs', SupportController::class .':docs')->setName('docs'); $route->get('/support/media', SupportController::class .':mediakit')->setName('mediakit');