| Current Path : /var/www/consult-e-syn/public_html/components/com_ats/Controller/ |
| Current File : /var/www/consult-e-syn/public_html/components/com_ats/Controller/NewTicket.php |
<?php
/**
* @package ats
* @copyright Copyright (c)2011-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Akeeba\TicketSystem\Site\Controller;
defined('_JEXEC') or die;
use Akeeba\TicketSystem\Admin\Controller\Mixin\UserTagsSaveAware;
use Akeeba\TicketSystem\Admin\Controller\Ticket;
use Akeeba\TicketSystem\Admin\Controller\TraitSaveUserTags;
use Akeeba\TicketSystem\Admin\Helper\Credits;
use Akeeba\TicketSystem\Admin\Helper\JoomlaUsers;
use Akeeba\TicketSystem\Admin\Helper\Permissions;
use Akeeba\TicketSystem\Site\Model\Attachments;
use Akeeba\TicketSystem\Site\Model\Attempts;
use Akeeba\TicketSystem\Site\Model\Categories;
use Akeeba\TicketSystem\Site\Model\Posts;
use Akeeba\TicketSystem\Site\Model\Tickets;
use Exception;
use FOF40\Container\Container;
use FOF40\Date\Date;
use FOF40\View\Exception\AccessForbidden;
use Joomla\CMS\Captcha\Captcha as JCaptcha;
use Joomla\CMS\Factory as JFactory;
use Joomla\CMS\HTML\HTMLHelper as JHtml;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Router\Route as JRoute;
use Joomla\CMS\Uri\Uri;
use Joomla\Registry\Registry;
use RuntimeException;
class NewTicket extends Ticket
{
use UserTagsSaveAware;
public function __construct(Container $container, array $config = [])
{
$config['modelName'] = 'Tickets';
$config['cacheableTasks'] = [];
parent::__construct($container, $config);
}
public function execute($task)
{
$allowed = [
'default',
'add',
'save',
'getcredits',
];
// Only allow a small subset of available tasks
if (!in_array($task, $allowed))
{
return false;
}
if ($task == 'default')
{
$task = $this->getCrudTask();
}
if ($task == 'read')
{
$task = 'add';
}
return parent::execute($task);
}
/**
* ACL checks are deferred to the main save task
*
* @return bool
*/
public function onBeforeSave()
{
return true;
}
public function save(): void
{
$this->csrfProtection();
// Fetch page parameters
/** @var \JApplicationSite $app */
$app = JFactory::getApplication();
$params = $app->getParams('com_ats');
// Get some basic information about the ticket and the post
$ticket = $this->input->get('ticket', [], 'array');
$post = $this->input->get('post', null, 'array', 2);
$ticket = is_array($ticket) ? $ticket : [];
$ticket['params'] = $this->input->get('params', [], 'array');
$ticket['params'] = is_array($ticket['params']) ? $ticket['params'] : [];
// Fetch the category. If the category cannot be loaded we will throw a 403.
$category_id = $this->getCategoryID($ticket, $params);
$ticket['catid'] = $category_id;
if ($category_id <= 0)
{
throw new AccessForbidden();
}
// Can I post to the category?
$perms = Permissions::getAclPrivileges($category_id);
if (!$perms['core.create'])
{
throw new AccessForbidden();
}
// Am I a manager or a guest?
$isManager = Permissions::isManager($category_id);
$isGuest = Permissions::getUser()->guest;
// Get the email and full name
$email = $this->input->getString('email', '');
$fullName = $this->input->getString('name', '');
/**
* Save everything in the session - First attempt
*
* At this stage we are saving the raw form data, before we have had a chance to verify Guest ticket settings. If a
* Guest ticket check fails (missing email / fullname or wrong CAPTCHA) we will be redirecting the user to the new ticket
* page. If we haven't saved the ticket info in the session the user would have to retype everything.
*/
if ($isGuest)
{
$this->saveNewTicketToSession($ticket, $post, $isManager, [
'ticket_email' => $email,
'ticket_name' => $fullName,
]);
}
// If I am a guest and the email or full name is empty return an error
if ($isGuest)
{
$email = empty($email) ? '' : trim($email);
$fullName = empty($fullName) ? '' : trim($fullName);
if (empty($email))
{
$this->failSaveWithMessage(Text::_('COM_ATS_NEWTICKET_ERR_GUESTPOST_EMAIL_MISSING'), $category_id);
return;
}
if (empty($fullName))
{
$this->failSaveWithMessage(Text::_('COM_ATS_NEWTICKET_ERR_GUESTPOST_FULLNAME_MISSING'), $category_id);
return;
}
}
// If a CAPTCHA was used but the solution is incorrect return an error
try
{
if ($isGuest)
{
$this->isValidCaptcha();
}
}
catch (RuntimeException $e)
{
$this->failSaveWithMessage($e->getMessage(), $category_id);
return;
}
// Set up the Created By. Takes into account "Create Post By" and "Guest Post" features.
$ticket['created_by'] = $this->getCreatedBy($isManager, $email, $fullName);
// I also have to set up Created On since FOF won't set it automatically when Created By is provided.
$ticket['created_on'] = Date::getInstance()->toSql();
// Cannot create user, missing parameters
if ($ticket['created_by'] === -1)
{
$this->failSaveWithMessage(Text::_('COM_ATS_NEWTICKET_ERR_GUESTPOST_NOREGISTRATION'), $category_id);
return;
}
// Missing email or full name, go back and retry
if ($ticket['created_by'] === false)
{
// This should have been caught above. If it didn't and we're here return a generic error message.
$this->failSaveWithMessage(Text::_('COM_ATS_NEWTICKET_ERR_GUESTPOST_OTHER'), $category_id);
return;
}
// User by that email already exists, redirect to login page
if ($ticket['created_by'] === true)
{
$redirect = $this->getRedirectUrl($category_id, true);
$url = 'index.php?option=com_users&view=login&return=' . base64_encode($redirect);
$this->setRedirect($url, Text::_('COM_ATS_NEWTICKET_ERR_GUESTPOST_USEREXISTS'));
return;
}
/**
* Save everything in the session - Second run.
*
* This runs no matter if we have a Guest or logged in user. Do NOT delete either of these two calls to
* saveNewTicketToSession. They are BOTH required.
*/
$this->saveNewTicketToSession($ticket, $post, $isManager);
// If I'm not allowed to post private tickets force the ticket status to public
if (!$perms['ats.private'])
{
$ticket['public'] = 1;
}
// Make sure the ticket has a title
if (empty($ticket['title']))
{
$this->failSaveWithMessage(Text::_('COM_ATS_ERR_NEWTICKET_NOTITLE'), $category_id);
return;
}
// --- Create the ticket
/** @var Tickets $model */
$model = $this->getModel()->reset();
// Add custom fields validation
$isValid = $model->isValid();
if (!$isValid)
{
$this->failSaveWithMessage(Text::_('COM_ATS_ERR_NEWTICKET_CUSTOM_FIELDS'), $category_id);
return;
}
// Check available credits
$error = $this->checkAvailableCredits($ticket, $post, $isManager);
if ($error !== false)
{
$this->failSaveWithMessage($error, $category_id);
return;
}
// Save the ticket
try
{
$model->save($ticket);
}
catch (Exception $e)
{
$this->failSaveWithMessage($e->getMessage(), $category_id);
return;
}
$ats_ticket_id = $model->getId();
$post['ats_ticket_id'] = $ats_ticket_id;
// --- Create attachment
/** @var Attachments $attachmentModel */
$attachmentModel = $this->container->factory->model('Attachments')->tmpInstance();
$attErrors = [];
$filter = $this->container->params->get('unsafe_uploads', 0) ? 'raw' : 'array';
$file = $this->input->files->get('attachedfile', [], $filter);
if (isset($file[0]) && ($file[0]['name'] != '') && $perms['ats.attachment'])
{
[$post['ats_attachment_id'], $attErrors] = $attachmentModel->manageUploads($file);
}
// --- Create post
$post['enabled'] = 1;
$status = true;
$postError = '';
/** @var Posts $pModel */
$pModel = $this->container->factory->model('posts')->tmpInstance();
/**
* Copy the Created By and On values from the ticket. Required for consistency in the Ticket as User (for managers) and
* Post as Guest features.
*/
$post['created_by'] = $ticket['created_by'];
$post['created_on'] = $ticket['created_on'];
// Save the post (only if we didn't have any error with attachments)
if (!$attErrors)
{
try
{
$pModel->save($post);
}
catch (Exception $e)
{
$postError = $e->getMessage();
$status = false;
}
}
// If I have any attachment, I have to check if they failed
if (isset($post['ats_attachment_id']))
{
$status = $status && empty($attErrors);
}
if (!$status)
{
// Remove the attachments
if (isset($post['ats_attachment_id']))
{
$attachments = explode(',', $post['ats_attachment_id']);
foreach ($attachments as $attachment)
{
if ($attachment)
{
$attachmentModel->delete($attachment);
}
}
}
// Remove the ticket
$model->delete($ats_ticket_id);
// Redirect
$this->failSaveWithMessage($postError, $category_id);
return;
}
// Update saved attachments with post id
if (isset($post['ats_attachment_id']))
{
$attachmentModel->updateSavedAttachments($post['ats_attachment_id'], $pModel->ats_post_id);
}
// Clear session
$this->container->platform->unsetSessionVar('ticket_title', 'com_ats.newTicketData');
$this->container->platform->unsetSessionVar('ticket_public', 'com_ats.newTicketData');
$this->container->platform->unsetSessionVar('custom', 'com_ats.newTicketData');
$this->container->platform->unsetSessionVar('post_content', 'com_ats.newTicketData');
$this->container->platform->unsetSessionVar('created_by', 'com_ats.newTicketData');
$this->container->platform->unsetSessionVar('ticket_email', 'com_ats.newTicketData');
$this->container->platform->unsetSessionVar('ticket_fullname', 'com_ats.newTicketData');
// Redirect
$url = 'index.php?option=com_ats&view=Tickets&category=' . $category_id . $this->getItemidURLSuffix();
if ($customURL = $this->input->getString('returnurl', ''))
{
$customURL = base64_decode($customURL);
$url = !empty($customURL) ? $customURL : $url;
}
$this->setRedirect($url, Text::_('COM_ATS_LBL_NEWTICKET_SAVED'));
return;
}
/**
* Get the credits required to post a ticket in a given category.
*
* This method belongs to the Category controller> SInce we're using Joomla! core categories we don't have a
* category controller, hence the misplaced method.
*
* @return void
*/
public function getcredits()
{
$catid = $this->input->getInt('catid', 0);
$public = $this->input->getInt('public', 1);
$priority = $this->input->getInt('priority', 10);
$credits = Credits::creditsRequired($catid, true, $public, $priority);
echo json_encode(['credits' => $credits]);
$this->container->platform->closeApplication();
}
/*
* Make sure the user is allowed to post in this category
*/
protected function onAfterSave()
{
$this->saveUserTags();
$ats_attempt_id = $this->input->getInt('ats_attempt_id');
// Did ATS show up some instant reply, but the user ignored them?
if ($ats_attempt_id)
{
/** @var Attempts $attempt */
$attempt = $this->container->factory->model('Attempts')->tmpInstance();
/** @var Tickets $ticket */
$ticket = $this->getModel();
$attempt->load($ats_attempt_id);
$bind['ats_ticket_id'] = $ticket->ats_ticket_id ?? 0;
$attempt->save($bind);
}
return true;
}
protected function onBeforeAdd()
{
// Fetch page parameters
/** @var \JApplicationSite $app */
$app = JFactory::getApplication();
$params = $app->getParams('com_ats');
$view = $this->getView();
// Set the layout to null to avoid issues from the request or the session
$this->layout = null;
// Fetch the category
$category_id = $this->input->getInt('category', 0);
/** @var Categories $catModel */
$catModel = $this->container->factory->model('Categories')->tmpInstance();
if (empty($category_id))
{
$category_id = $params->get('category', 0);
}
if (empty($category_id))
{
// No category specified. We'll have to show a landing page.
$this->layout = 'landing';
$this->getView()->setLayout('landing');
$this->getView()->categories = $catModel->get(true);
return true;
}
$categories = $catModel->category($category_id)->get(true);
/** @var Categories $category */
$category = $categories->first();
if (!$category)
{
// Category not found, this means that the user is not allowed to access it
return false;
}
// Get ACL permissions
$perms = Permissions::getAclPrivileges($category->id);
// Can I post to the category? If I can't, throw a 403!
if (!$perms['core.create'])
{
// Am I a guest user?
if (Permissions::getUser()->guest)
{
$returnURL = base64_encode(Uri::getInstance()->toString());
$url = JRoute::_('index.php?option=com_users&view=login&return=' . $returnURL, false);
$this->container->platform->redirect($url, '307', Text::_('JLIB_APPLICATION_ERROR_ACCESS_FORBIDDEN'));
}
return false;
}
// -- If I am a manager allow specifying the created_by user (post as another user)
$isManager = Permissions::isManager($category->id);
// Load session and push data
// @TODO Load everything inside the $cache variable, in the same way we do in Akeeba Subs
$custom = $this->container->platform->getSessionVar('custom', [], 'com_ats.newTicketData');
// Make sure that it's an array, so it will be handled correctly by ats plugins (ie customfields)
if (!is_array($custom))
{
$custom = json_decode($custom, true);
}
$cache['params'] = $custom;
$view->ticket_title = $this->container->platform->getSessionVar('ticket_title', '', 'com_ats.newTicketData');
$view->ticket_public = $this->container->platform->getSessionVar('ticket_public', null, 'com_ats.newTicketData');
$view->post_content = $this->container->platform->getSessionVar('post_content', '', 'com_ats.newTicketData');
$view->ticket_email = $this->container->platform->getSessionVar('ticket_email', '', 'com_ats.newTicketData');
$view->ticket_name = $this->container->platform->getSessionVar('ticket_name', '', 'com_ats.newTicketData');
$view->cache = $cache;
$view->captchaPlugin = $this->doINeedCaptcha();
if ($isManager)
{
$view->created_by = $this->container->platform->getSessionVar('created_by', Permissions::getUser()->id, 'com_ats.newTicketData');
}
/**
* Um, doing that would reset the form if the user pressed reload. We don't need to do this; the session is unset in the
* Controller, anyway, just as soon as we create the new ticket.
*/
/*
$this->container->platform->unsetSessionVar('ticket_title', 'com_ats.newTicketData');
$this->container->platform->unsetSessionVar('ticket_public', 'com_ats.newTicketData');
$this->container->platform->unsetSessionVar('ticket_email', 'com_ats.newTicketData');
$this->container->platform->unsetSessionVar('ticket_name', 'com_ats.newTicketData');
$this->container->platform->unsetSessionVar('post_content', 'com_ats.newTicketData');
*/
// Push permissions
$view->allow_private = $perms['ats.private'];
$view->allow_attachment = $perms['ats.attachment'];
// Push page parameters
$view->pageparams = $params;
// Push category object
$view->category = $category;
JHtml::_('behavior.keepalive');
return true;
}
/**
* Get the ticket category ID and make sure the category does exist. The category ID is either provided in the form
* (in the catid field) or set in the page parameters (e.g. we have a menu item pointing to a specific category)
*
* @param array $ticket The ticket information
* @param Registry $params Page parameters
*
* @return int The category ID or -1 if it's not found
*
* @since 2.4.1
*/
private function getCategoryID($ticket, $params)
{
$category_id = 0;
if (array_key_exists('catid', $ticket))
{
$category_id = $ticket['catid'];
}
if (empty($category_id))
{
$category_id = $params->get('category', 0);
}
/** @var Categories $catModel */
$catModel = $this->container->factory->model('Categories')->tmpInstance();
$categories = $catModel->category($category_id)->get(true);
$category = $categories->first();
if (!$category)
{
// Category not found, this means that the user is not allowed to access it
return -1;
}
return $category_id;
}
/**
* Save the basic ticket information (everything except custom fields and uploads) into the session. This lets us
* show an error without the user losing all of their ticket information.
*
* @param array $ticket The ticket information
* @param array $post The post information
* @param bool $isManager Am I a manager?
* @param array $extra_data Any additional data to save to the session
*
* @since 2.4.1
*/
private function saveNewTicketToSession(&$ticket, &$post, $isManager, $extra_data = [])
{
if (!array_key_exists('public', $ticket))
{
$ticket['public'] = 1;
}
if (!array_key_exists('title', $ticket))
{
$ticket['title'] = '';
}
if (!array_key_exists('content', $post))
{
$post['content'] = '';
}
if (!array_key_exists('content_html', $post))
{
$post['content_html'] = '';
}
if (!array_key_exists('created_by', $post))
{
$post['created_by'] = null;
}
$postcontent = empty($post['content']) ? $post['content_html'] : $post['content'];
$platform = $this->container->platform;
$platform->setSessionVar('ticket_title', $ticket['title'], 'com_ats.newTicketData');
$platform->setSessionVar('ticket_public', $ticket['public'], 'com_ats.newTicketData');
$platform->setSessionVar('custom', $ticket['params'], 'com_ats.newTicketData');
$platform->setSessionVar('post_content', $postcontent, 'com_ats.newTicketData');
if ($isManager)
{
$platform->setSessionVar('created_by', $ticket['created_by'], 'com_ats.newTicketData');
}
if (!empty($extra_data))
{
foreach ($extra_data as $k => $v)
{
$platform->setSessionVar($k, $v, 'com_ats.newTicketData');
}
}
}
/**
* Check if the user has the necessary credits required to file the ticket.
*
* @param array $ticket The ticket information
* @param array $post The post information
* @param bool $isManager Is this a manager?
*
* @return bool|string Boolean false or an error message string
*
* @since 2.4.1
*/
private function checkAvailableCredits($ticket, $post, $isManager)
{
$error = false;
if (!$this->container->params->get('showcredits', 0))
{
return $error;
}
$userId = $post['created_by'];
// TODO check vs priority
$hasCredits = Credits::haveEnoughCredits($userId, $ticket['catid'], true, $ticket['public'], false);
if (!$hasCredits)
{
if ($isManager)
{
$error = Text::_('COM_ATS_ERR_NEWTICKET_NOT_ENOUGH_CREDITS_USER');
}
else
{
$error = Text::_('COM_ATS_ERR_NEWTICKET_NOT_ENOUGH_CREDITS');
}
}
return $error;
}
/**
* Fail the save operation with the message. Redirects back to the ticket submission page. If $error is empty a
* redirection without a message is performed.
*
* @param string $error Message to show to the user.
* @param int $category_id ATS category ID
* @param bool $showCustomFieldsValidation Should I display the custom fields validation status when I
* return?
*
*
* @since 2.4.1
*/
private function failSaveWithMessage($error, $category_id, $showCustomFieldsValidation = true)
{
$url = $this->getRedirectUrl($category_id, $showCustomFieldsValidation);
if (empty($error))
{
$this->setRedirect($url);
return;
}
$this->setRedirect($url, $error, 'error');
}
/**
* Get the appropriate Created By user ID.
*
* If the ticket is submitted by a manager and there's a created_by form field (Post As Another User) we use that.
* If not, or if the ticket is filed by any other registered user we return the currently logged in user's ID.
* If it's a Guest we try to create a user using the email and full name provided and return that user ID. This
* could fail. If user registration is disabled we return false. If the email or full name is missing we return -1.
* If there is another user by that email we return true because the user needs to log in instead.
*
* @param bool $isManager Is this being filed by a Manager (support staff)?
*
* @return int|bool User ID, -1 (cannot create user), false (no email / full name provided) or true (user by that
* email already exists)
*
* @since 2.4.1
*/
private function getCreatedBy($isManager, $email, $fullName)
{
// Get a reference to the currently logged in user
$user = Permissions::getUser();
// If I am a manager, am I posting as another user? This has priority over anything else.
if ($isManager)
{
$custom_created_by = $this->input->getInt('created_by', 0);
if ($custom_created_by)
{
return $custom_created_by;
}
}
// If I am any other logged in user return the user ID
if (!$user->guest)
{
return $user->id;
}
// If I'm here I am a guest. If there is no email address in the request return boolean false
if (empty($email) || empty($fullName))
{
return false;
}
// If I have an email, is there a user already assigned to it? If so, return boolean true
$ju = new JoomlaUsers($this->container);
$userId = $ju->getUserIdByEmail($email);
if ($userId)
{
return true;
}
// No such user exists. Create a new user and send the user creation email etc.
try
{
$forceActivate = $this->container->params->get('forceGuestActivation', 0);
[, $userId] = $ju->createUser($email, $fullName, $forceActivate);
}
catch (Exception $e)
{
return -1;
}
return $userId;
}
/**
* Used for Guest submissions. Checks if the CAPTCHA is enabled for Guest posts and checks if it's been filled in.
*
* @throws \RuntimeException
* @since 2.4.1
*
*/
private function isValidCaptcha()
{
$captchaPlugin = $this->doINeedCaptcha();
if ($captchaPlugin === false)
{
return;
}
// Validate the CAPTCHA solution
$code = $this->input->get('captcha', null, 'raw');
$captcha = JCaptcha::getInstance($captchaPlugin, ['namespace' => 'com_ats_newTicket']);
$captcha->checkAnswer($code);
}
/**
* Gets the redirection URL which gets us back to the ticket submission page
*
* @param int $category_id ATS category ID
* @param bool $showCustomFieldsValidation Should I display the custom fields validation status when I return?
*
* @return string The URL
*
* @since 2.4.1
*/
private function getRedirectUrl($category_id, $showCustomFieldsValidation)
{
$url = 'index.php?option=com_ats&view=NewTicket&category=' . $category_id;
if ($showCustomFieldsValidation)
{
$url .= '&formsubmit=1';
}
$url .= $this->getItemidURLSuffix();
$url = JRoute::_($url, false);
return $url;
}
/**
*
* @return bool
*/
private function doINeedCaptcha()
{
// Do I even need to check for a CAPTCHA?
$needsCaptcha = $this->container->params->get('showGuestCAPTCHA', 1) == 1;
if (!$needsCaptcha)
{
return false;
}
// Get the site-wide CAPTCHA plugin
$app = JFactory::getApplication();
$captchaPlugin = $app->get('captcha');
if ($this->container->platform->isFrontend())
{
/** @var \JApplicationSite $app */
$captchaPlugin = $app->getParams()->get('captcha', $captchaPlugin);
}
// No CAPTCHA plugin is activated?
if (empty($captchaPlugin) || ($captchaPlugin == 'none'))
{
return false;
}
return $captchaPlugin;
}
}