00d44256b4
Rebrand all 17 sub-extensions from mokowaas to mokosuite naming, including component, plugins, modules, task plugins, and webservices. Updates package manifest, workflows, docs, wiki, and issue templates. Adds new plg_system_mokosuite_license extension.
268 lines
6.6 KiB
PHP
268 lines
6.6 KiB
PHP
<?php
|
|
/**
|
|
* @package MokoSuite
|
|
* @subpackage com_mokosuite.site
|
|
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
|
|
* @license GNU General Public License version 3 or later; see LICENSE
|
|
*/
|
|
|
|
namespace Moko\Component\MokoSuite\Site\Controller;
|
|
|
|
defined('_JEXEC') or die;
|
|
|
|
use Joomla\CMS\Factory;
|
|
use Joomla\CMS\Language\Text;
|
|
use Joomla\CMS\MVC\Controller\BaseController;
|
|
use Joomla\CMS\Router\Route;
|
|
use Joomla\CMS\Session\Session;
|
|
|
|
class DisplayController extends BaseController
|
|
{
|
|
protected $default_view = 'tickets';
|
|
|
|
public function display($cachable = false, $urlparams = [])
|
|
{
|
|
$user = Factory::getApplication()->getIdentity();
|
|
|
|
if ($user->guest)
|
|
{
|
|
Factory::getApplication()->enqueueMessage('Please log in to access the support portal.', 'warning');
|
|
Factory::getApplication()->redirect(Route::_(
|
|
'index.php?option=com_users&view=login&return=' . base64_encode('index.php?option=com_mokosuite&view=tickets'),
|
|
false
|
|
));
|
|
|
|
return;
|
|
}
|
|
|
|
return parent::display($cachable, $urlparams);
|
|
}
|
|
|
|
/**
|
|
* Submit a new ticket.
|
|
*/
|
|
public function submitTicket()
|
|
{
|
|
Session::checkToken() or die(Text::_('JINVALID_TOKEN'));
|
|
|
|
$user = Factory::getApplication()->getIdentity();
|
|
|
|
if ($user->guest)
|
|
{
|
|
$this->jsonResponse(['success' => false, 'message' => 'Please log in.']);
|
|
return;
|
|
}
|
|
|
|
$input = Factory::getApplication()->getInput();
|
|
|
|
// Use admin TicketsModel
|
|
$model = $this->getModel('Tickets', 'Administrator');
|
|
|
|
$this->jsonResponse($model->createTicket([
|
|
'subject' => $input->getString('subject', ''),
|
|
'body' => $input->getRaw('body', ''),
|
|
'priority' => $input->getString('priority', 'normal'),
|
|
'category_id' => $input->getInt('category_id', 0),
|
|
]));
|
|
}
|
|
|
|
/**
|
|
* Submit a reply.
|
|
*/
|
|
public function submitReply()
|
|
{
|
|
Session::checkToken() or die(Text::_('JINVALID_TOKEN'));
|
|
|
|
$user = Factory::getApplication()->getIdentity();
|
|
$input = Factory::getApplication()->getInput();
|
|
|
|
if ($user->guest)
|
|
{
|
|
$this->jsonResponse(['success' => false, 'message' => 'Please log in.']);
|
|
return;
|
|
}
|
|
|
|
$ticketId = $input->getInt('ticket_id', 0);
|
|
$model = $this->getModel('Tickets', 'Administrator');
|
|
$ticket = $model->getTicket($ticketId);
|
|
|
|
if (!$ticket)
|
|
{
|
|
$this->jsonResponse(['success' => false, 'message' => 'Ticket not found.']);
|
|
return;
|
|
}
|
|
|
|
// Customers can only reply to their own tickets; staff can reply to any
|
|
if ((int) $ticket->created_by !== $user->id && !$this->isStaff($user))
|
|
{
|
|
$this->jsonResponse(['success' => false, 'message' => 'Access denied.']);
|
|
return;
|
|
}
|
|
|
|
// Staff replies from frontend are not internal notes
|
|
$this->jsonResponse($model->addReply(
|
|
$ticketId,
|
|
$input->getRaw('body', ''),
|
|
false
|
|
));
|
|
}
|
|
|
|
/**
|
|
* Update ticket status (staff/manager only from frontend).
|
|
*/
|
|
public function updateStatus()
|
|
{
|
|
Session::checkToken() or die(Text::_('JINVALID_TOKEN'));
|
|
|
|
$user = Factory::getApplication()->getIdentity();
|
|
|
|
if (!$this->isStaff($user))
|
|
{
|
|
$this->jsonResponse(['success' => false, 'message' => 'Access denied.']);
|
|
return;
|
|
}
|
|
|
|
$input = Factory::getApplication()->getInput();
|
|
$model = $this->getModel('Tickets', 'Administrator');
|
|
|
|
$this->jsonResponse($model->updateStatus(
|
|
$input->getInt('ticket_id', 0),
|
|
$input->getString('status', '')
|
|
));
|
|
}
|
|
|
|
/**
|
|
* Assign a ticket (manager only from frontend).
|
|
*/
|
|
public function assignTicket()
|
|
{
|
|
Session::checkToken() or die(Text::_('JINVALID_TOKEN'));
|
|
|
|
$user = Factory::getApplication()->getIdentity();
|
|
|
|
if (!$user->authorise('mokosuite.tickets.assign', 'com_mokosuite'))
|
|
{
|
|
$this->jsonResponse(['success' => false, 'message' => 'Access denied.']);
|
|
return;
|
|
}
|
|
|
|
$input = Factory::getApplication()->getInput();
|
|
$ticketId = $input->getInt('ticket_id', 0);
|
|
$assignTo = $input->getInt('assigned_to', 0);
|
|
|
|
try
|
|
{
|
|
$db = Factory::getDbo();
|
|
$db->setQuery(
|
|
$db->getQuery(true)
|
|
->update($db->quoteName('#__mokosuite_tickets'))
|
|
->set($db->quoteName('assigned_to') . ' = ' . ($assignTo ?: 'NULL'))
|
|
->set($db->quoteName('modified') . ' = ' . $db->quote(Factory::getDate()->toSql()))
|
|
->where($db->quoteName('id') . ' = ' . $ticketId)
|
|
)->execute();
|
|
|
|
$this->jsonResponse(['success' => true, 'message' => 'Ticket assigned.']);
|
|
}
|
|
catch (\Throwable $e)
|
|
{
|
|
$this->jsonResponse(['success' => false, 'message' => $e->getMessage()]);
|
|
return;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Submit a data privacy request from frontend.
|
|
*/
|
|
public function submitDataRequest()
|
|
{
|
|
Session::checkToken() or die(Text::_('JINVALID_TOKEN'));
|
|
|
|
$user = Factory::getApplication()->getIdentity();
|
|
|
|
if ($user->guest)
|
|
{
|
|
$this->jsonResponse(['success' => false, 'message' => 'Please log in.']);
|
|
return;
|
|
}
|
|
|
|
$type = Factory::getApplication()->getInput()->getString('type', '');
|
|
$model = new \Moko\Component\MokoSuite\Administrator\Model\PrivacyModel();
|
|
|
|
$this->jsonResponse($model->createRequest($user->id, $type, 'Submitted via self-service portal'));
|
|
}
|
|
|
|
/**
|
|
* Check if user is support staff (can manage tickets beyond their own).
|
|
*/
|
|
private function isStaff($user): bool
|
|
{
|
|
if ($user->guest)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Super admins always staff
|
|
if ($user->authorise('core.admin'))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// Anyone with mokosuite.tickets ACL on the component is staff
|
|
return $user->authorise('mokosuite.tickets', 'com_mokosuite');
|
|
}
|
|
|
|
/**
|
|
* Search KB articles via Smart Search (com_finder).
|
|
*/
|
|
public function searchKb()
|
|
{
|
|
$query = Factory::getApplication()->getInput()->getString('q', '');
|
|
|
|
if (strlen($query) < 3)
|
|
{
|
|
$this->jsonResponse(['results' => []]);
|
|
}
|
|
|
|
try
|
|
{
|
|
$db = Factory::getDbo();
|
|
$escaped = $db->quote('%' . $db->escape($query, true) . '%');
|
|
|
|
$results = $db->setQuery(
|
|
$db->getQuery(true)
|
|
->select([
|
|
$db->quoteName('l.link_id'),
|
|
$db->quoteName('l.title'),
|
|
$db->quoteName('l.url'),
|
|
$db->quoteName('l.description'),
|
|
])
|
|
->from($db->quoteName('#__finder_links', 'l'))
|
|
->where($db->quoteName('l.published') . ' = 1')
|
|
->where('(' . $db->quoteName('l.title') . ' LIKE ' . $escaped
|
|
. ' OR ' . $db->quoteName('l.description') . ' LIKE ' . $escaped . ')')
|
|
->order($db->quoteName('l.title') . ' ASC')
|
|
->setLimit(8)
|
|
)->loadObjectList() ?: [];
|
|
|
|
foreach ($results as $r)
|
|
{
|
|
$r->description = mb_substr(strip_tags($r->description ?? ''), 0, 150);
|
|
}
|
|
|
|
$this->jsonResponse(['results' => $results]);
|
|
}
|
|
catch (\Throwable $e)
|
|
{
|
|
$this->jsonResponse(['results' => []]);
|
|
}
|
|
}
|
|
|
|
private function jsonResponse(array $data): void
|
|
{
|
|
$app = Factory::getApplication();
|
|
$app->setHeader('Content-Type', 'application/json');
|
|
echo json_encode($data);
|
|
$app->close();
|
|
}
|
|
}
|