Your IP : 216.73.217.142


Current Path : /var/www/consult-e-syn/public_html/components/com_ats/Controller/
Upload File :
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;
	}
}