Your IP : 216.73.217.142


Current Path : /var/www/consult-e-syn/public_html/plugins/ats/mailfetch/
Upload File :
Current File : /var/www/consult-e-syn/public_html/plugins/ats/mailfetch/mailfetch.php

<?php
/**
 * @package   ats
 * @copyright Copyright (c)2011-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

defined('_JEXEC') or die;

use Akeeba\TicketSystem\Admin\Helper\Permissions;
use Akeeba\TicketSystem\MailFetch\EmailCheck;
use Akeeba\TicketSystem\MailFetch\TimeoutException;
use Composer\Autoload\ClassLoader;
use FOF40\Container\Container;
use FOF40\Timer\Timer;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Log\Log;
use Joomla\CMS\Mail\Mail as JMail;
use Joomla\CMS\Plugin\CMSPlugin;
use Joomla\CMS\Uri\Uri;

/**
 * Download and process email to create new tickets and ticket replies
 *
 * @since 3.2.0
 */
class plgAtsMailfetch extends CMSPlugin
{
	const atsCommandName = 'mailfetch';

	/**
	 * @inheritDoc
	 */
	public function __construct(&$subject, $config = [])
	{
		parent::__construct($subject, $config);

		if (!defined('FOF40_INCLUDED') && !@include_once(JPATH_LIBRARIES . '/fof40/include.php'))
		{
			throw new RuntimeException('This extension requires FOF 4.', 500);
		}

		$this->loadLanguage();

		// Load the Composer autoloader. Necessary to have access to the Horde library on CLI.
		$autoloaderPath = __DIR__ . '/vendor/autoload.php';

		if (file_exists($autoloaderPath))
		{
			/** @var ClassLoader $autoloader */
			$autoloader = require_once $autoloaderPath;

			$autoloader->addPsr4('Akeeba\\TicketSystem\\MailFetch\\', __DIR__ . '/library');
		}
	}

	/**
	 * Returns information about the CRON task provided by this plugin
	 *
	 * @return  array
	 *
	 * @since   3.2.0
	 */
	public function onAtsCronTaskInfo()
	{
		return [
			'command'     => self::atsCommandName,
			'label'       => 'PLG_ATS_' . $this->_name . '_TASK_LABEL',
			'description' => 'PLG_ATS_' . $this->_name . '_TASK_DESC',
		];
	}

	/**
	 * Handles the ATS CRON tasks.
	 *
	 * @param   string  $command  The command name ATS CRON was asked to execute.
	 * @param   array   $options  Any options passed to this CRON job
	 *
	 * @return  bool|null  True on success, null if it's not the expected command name.
	 * @since   3.2.0
	 */
	public function onAtsCronTask($command, array $options = [])
	{
		// Make sure we are calling the correct command
		if ($command != self::atsCommandName)
		{
			return null;
		}

		$timeLimit = array_key_exists('time_limit', $options) ? $options['time_limit'] : 86400;
		$timer     = new Timer($timeLimit);

		$container  = Container::getInstance('com_ats');
		$emailCheck = new EmailCheck($container);

		// Load Joomla global language file. Needed to print nice warning messages (ie attachment upload error)
		$lang = $container->platform->getLanguage();
		$lang->load('lib_joomla', JPATH_ADMINISTRATOR);

		try
		{
			$emailCheck->checkEmail($timer);

			Log::add('Done retrieving email messages', Log::DEBUG, 'ats.cron');
		}
		catch (TimeoutException $e)
		{
			Log::add(sprintf('I have reached the time limit of %u second(s). Stopping for now.', $timeLimit), Log::DEBUG, 'ats.cron');
		}
		catch (Exception $e)
		{
			Log::add('Failed to retrieve email messages', Log::DEBUG, 'ats.cron');
		}

		return true;
	}

	/**
	 * Called when ATS is sending an email.
	 *
	 * This is used to add the ticket reply line at the top of the email.
	 *
	 * @param   string  $subject    The subject of the email being send
	 * @param   string  $replyLine  The body of the email being sent
	 * @param   JMail   $mailer     The Joomla mailer object
	 * @param   array   $mailInfo   Information about the email being sent
	 * @param   string  $toWhom     To whom this email is sent: 'manager', 'owner' or 'subscriber'
	 *
	 * @return  void
	 * @see     plgAtsPostemail::_sendEmailToUser
	 *
	 * @since   3.2.0
	 */
	public function onATSSendEmail(&$subject, &$body, JMail $mailer, array &$mailInfo, $toWhom = 'owner')
	{
		// Make sure the Reply by Email feature is enabled
		if ($this->params->get('replybyemail', 0) != 1)
		{
			return;
		}

		// If the "Only for managers" option is enabled make sure it's a mail sent to a category manager
		$emailReplyOnlyForManager = $this->params->get('emailadminonly', 0);

		if ($emailReplyOnlyForManager && ($toWhom != 'manager'))
		{
			return;
		}

		// Get the reply line's contents as HTML
		$replyLine = sprintf(
			"<p>!-!- %s {ticketid:%s} -!-!</p>",
			Text::_('PLG_ATS_POSTEMAIL_REPLYABOVE'),
			$mailInfo['id']
		);

		/**
		 * Look for the opening <body> tag and inject the reply line right after it opens. Otherwise email clients will
		 * rightfully complain about the HTML in the message being broken.
		 *
		 * If the opening body tag is not found we will simply prefix our email body with the reply line and hope for the
		 * best.
		 */
		$body = preg_replace('#<body[^>]*>#i', '$0' . $replyLine, $body, -1, $count);

		if ($count < 1)
		{
			$body = $replyLine . $body;
		}

		// Add a custom header, too
		try
		{
			$mailer->addCustomHeader('X-ATS-ticketid:' . $mailInfo['id']);
		}
		catch (Exception $e)
		{
			// JMail died on us. No problem, we still have the reply line.
		}
	}

	/**
	 * Redirect the client to the GMail / G Suite OAuth2 consent page
	 *
	 * @return  string  HTML to perform the redirection
	 *
	 * @since   3.2.0
	 */
	public function onAjaxAtsMailfetchGmailConsent()
	{
		$container = Container::getInstance('com_ats');

		// Sanity check: must be backend
		if (!$container->platform->isBackend())
		{
			throw new RuntimeException(Text::_('JERROR_ALERTNOAUTHOR'));
		}

		// Sanity check: user must have edit privilege for com_plugins
		if (!Permissions::getUser()->authorise('core.edit', 'com_plugins'))
		{
			throw new RuntimeException(Text::_('JERROR_ALERTNOAUTHOR'));
		}

		// Sanity check: I must have the correct anti-CSRF token
		if ($container->input->get($container->platform->getToken(), '0') != 1)
		{
			throw new RuntimeException(Text::_('JERROR_ALERTNOAUTHOR'));
		}

		// Sanity check: I must be sent the client ID and api secret
		$clientId  = $container->input->get('client_id', '');
		$apiSecret = $container->input->get('api_secret', '');

		if (empty($clientId) || empty($apiSecret))
		{
			throw new RuntimeException(Text::_('JERROR_ALERTNOAUTHOR'));
		}

		// Save Client ID and Secret Key in the session
		$container->platform->setSessionVar('client_id', $clientId, 'plg_ats_mailfetch');
		$container->platform->setSessionVar('api_secret', $apiSecret, 'plg_ats_mailfetch');

		$authOpenUrl = 'https://accounts.google.com/o/oauth2/v2/auth?' . http_build_query([
				'client_id'     => $clientId,
				'redirect_uri'  => Uri::base() . 'index.php?option=com_ajax&group=ats&plugin=AtsMailfetchGmailCode&format=raw',
				'scope'         => 'https://mail.google.com/ ',
				'access_type'   => 'offline',
				'prompt'        => 'consent',
				'response_type' => 'code',
				'state'         => $container->platform->getToken(),
			]);

		header('Location: ' . $authOpenUrl, true, 307);

		return <<< HTML
<html lang="en-GB"><head><title>Redirection</title></head><body><p>One moment, please...</p><script type="application/javascript">window.location='$authOpenUrl';</script></body></html>
HTML;
	}

	/**
	 * Exchange the GMail / G Suite OAuth2 code with a pair of access and refresh token, then apply them to the plugin.
	 *
	 * @return  string  HTML to apply the tokens to the plugin configuration page
	 *
	 * @since   3.2.0
	 */
	public function onAjaxAtsMailfetchGmailCode()
	{
		$container = Container::getInstance('com_ats');

		// Sanity check: must be backend
		if (!$container->platform->isBackend())
		{
			throw new RuntimeException(Text::_('JERROR_ALERTNOAUTHOR'));
		}

		// Sanity check: user must have edit privilege for com_plugins
		if (!Permissions::getUser()->authorise('core.edit', 'com_plugins'))
		{
			throw new RuntimeException(Text::_('JERROR_ALERTNOAUTHOR'));
		}

		// Sanity check: check for error
		$error            = $container->input->get('error', '');
		$errorDescription = $container->input->get('error_description', '');

		if (!empty($error))
		{
			return $this->OAuth2Error($error, $errorDescription);
		}

		// Sanity check: I need client ID and api secret in the session
		$clientId  = $container->platform->getSessionVar('client_id', '', 'plg_ats_mailfetch');
		$apiSecret = $container->platform->getSessionVar('api_secret', '', 'plg_ats_mailfetch');

		if (empty($clientId) || empty($apiSecret))
		{
			throw new RuntimeException(Text::_('JERROR_ALERTNOAUTHOR'));
		}

		// Sanity check: must have all expected incoming parameters (code and state)
		$code  = $container->input->get('code', '', 'raw', 2);
		$state = $container->input->get('state', '', 'raw', 2);

		if (empty($code) || empty($state))
		{
			throw new RuntimeException(Text::_('JERROR_ALERTNOAUTHOR'));
		}

		// Sanity check: state must match anti-CSRF token
		if ($state != $container->platform->getToken())
		{
			throw new RuntimeException(Text::_('JERROR_ALERTNOAUTHOR'));
		}

		// Exchange the code with a set of tokens
		$query = http_build_query([
			'code'          => $code,
			'client_id'     => $clientId,
			'client_secret' => $apiSecret,
			'redirect_uri'  => Uri::base() . 'index.php?option=com_ajax&group=ats&plugin=AtsMailfetchGmailCode&format=raw',
			'grant_type'    => 'authorization_code',
		], '', '&');
		$url   = 'https://www.googleapis.com/oauth2/v4/token';

		$ch = curl_init($url);

		$caCertPath = class_exists('\\Composer\\CaBundle\\CaBundle')
			? \Composer\CaBundle\CaBundle::getBundledCaBundlePath()
			: JPATH_LIBRARIES . '/src/Http/Transport/cacert.pem';

		curl_setopt_array($ch, [
			CURLOPT_SSL_VERIFYPEER => true,
			CURLOPT_VERBOSE        => true,
			CURLOPT_HEADER         => false,
			CURLINFO_HEADER_OUT    => false,
			CURLOPT_RETURNTRANSFER => true,
			CURLOPT_CAINFO         => $caCertPath,
			CURLOPT_FOLLOWLOCATION => true,
			CURLOPT_POST           => 1,
			CURLOPT_POSTFIELDS     => $query,
			CURLOPT_HTTPHEADER     => [
				'Content-Type: application/x-www-form-urlencoded',
			],
		]);

		$response = curl_exec($ch);
		$errNo    = curl_errno($ch);
		$error    = curl_error($ch);
		curl_close($ch);

		// Did cURL die?
		if ($errNo)
		{
			$errorDescription = <<< HTML
An error occurred communicating with Google Mail. Technical information:<br/><br/>
Error Number: $errNo<br/>
Error Description: $error<br/><br/>
Please try authenticating with Google Mail later. If the problem persists please file a bug report to www.akeeba.com and copy this error message.
HTML;

			return $this->OAuth2Error('curl_error', $errorDescription);
		}

		$result = @json_decode($response, true);

		// Did we receive invalid JSON?
		if (empty($result))
		{
			$error            = 'invalid_json';
			$errorDescription = 'Google Mail failed to response with a valid token. Please try again later.';

			return $this->OAuth2Error($error, $errorDescription);
		}

		// Do we have an error reported by Google Mail?
		if (isset($result['error']))
		{
			$error = $result['error'];

			if (isset($result['error_uri']))
			{
				header('Location: ' . $result['error_uri'], true, 307);

				return <<< HTML
<html lang="en-GB"><head><title>Error Redirection</title></head><body><p>One moment, please...</p><script type="application/javascript">window.location='{$result['error_uri']}';</script></body></html>
HTML;
			}

			if (isset($result['error_description']))
			{
				$errorDescription = $result['error_description'];
			}

			return $this->OAuth2Error($error, $errorDescription);
		}

		$access_token  = isset($result['access_token']) ? $result['access_token'] : '';
		$refresh_token = isset($result['refresh_token']) ? $result['refresh_token'] : '';

		return $this->applyReceivedOAuth2Tokens($access_token, $refresh_token);
	}

	/**
	 * OAuth2 authentication callback handler.
	 *
	 * The OAuth2 flow goes like this:
	 *
	 * 1. The user clicks on the Authenticate button. This calls a JS function e.g. ats_mailfetch_gmail_authenticate
	 *    which is defined in the custom JFormField class (e.g. JFormFieldAtsgmailbutton).
	 *
	 * 2. That JS function opens a pop-up window to a com_ajax URL on your site e.g.:
	 *    https://www.example.com/administrator/index.php?option=com_ajax&group=ats&plugin=AtsMailfetchGmailConsent
	 *    &format=raw&client_id=foobar&api_secret=secret&tokenValueHere=1
	 *
	 * 3. That URL redirects you to the mail service's consent screen
	 *
	 * 4. The service-hosted consent screen redirects back to a com_ajax URL on your site that exchanges the code with
	 *    API tokens and calls the applyReceivedOAuth2Tokens method e.g.:
	 *    https://www.example.com/administrator/index.php?option=com_ajax&group=ats&plugin=AtsMailfetchGmailCode
	 *    &format=raw&code=someLongCodeToExchangeHere
	 *
	 * 5. This method displays a short HTML page inside the popup window which calls the ats_mailfetch_token_callback
	 *    JS function in the main window. This JS function is defined in the custom JFormField class (e.g.
	 *    JFormFieldAtsgmailbutton).
	 *
	 * 5. That JS function fills in the plugin parameters form fields and closes the popup.
	 *
	 * Since com_ajax can be called by anyone we perform the following security checks before allowing it to display
	 * the
	 * short HTML page:
	 *
	 * - com_ajax must be called from the backend of the site.
	 *
	 * - A user must be logged in AND they must have the Manage privilege for Akeeba Ticket System.
	 *
	 * If either check fails you get an HTTP 403 error.
	 *
	 * @return  string
	 *
	 * @since   3.2.0
	 */
	private function applyReceivedOAuth2Tokens($accessToken, $refreshToken)
	{
		$data = (object) [
			'access_token'  => $accessToken,
			'refresh_token' => $refreshToken,
		];

		$serialisedData = json_encode($data);

		return <<< HTML
<html lang="en-GB">
<head>
	<title>Token callback</title>
</head>
<body>
<p>One moment, please...</p>
<script type="application/javascript">
	window.opener.ats_mailfetch_token_callback($serialisedData);
</script>
</body>
</html>
HTML;
	}

	/**
	 * Display an error coming from OAuth2 authentication
	 *
	 * @param   string  $error             Error code
	 * @param   string  $errorDescription  Longer description
	 *
	 * @return  string  HTML to display
	 *
	 * @since   3.2.0
	 */
	private function OAuth2Error($error, $errorDescription)
	{
		$description = <<< HTML
The mail service reported that there was an error. Unfortunately, no other information was provided. We apologize for the vague error message.
HTML;

		switch ($error)
		{
			case 'invalid_request':
				$description = <<< HTML
The mail service reported that there was a problem with the request. Please retry the authentication later.
HTML;

				break;

			case 'invalid_client':
				$description = <<< HTML
The mail service reported that they didn't recognise the API application. Please make sure you have filled in the API Client ID and API Secret Key correctly before clicking the Authentication button again.
HTML;

				break;

			case 'invalid_grant':
				$description = <<< HTML
The mail service reported that you did not log in correctly or did not grant permission to access your mail account. Please retry the authentication.
HTML;

				break;

			case 'unauthorized_client':
				$description = <<< HTML
The mail service reported that your API application is not allowed to request access to your account. Please make sure that you have create an API application under the correct service account and it's set up to serve a hosted web application (NOT an installed application or JavaScript application). Double check the redirection URLs you've set up in your API application so they match our documentation. Moreover, make sure you have filled in the API Client ID and API Secret Key correctly <strong>and</strong> that you have selected the correct scopes in your API application (per our documentation) before clicking the Authentication button again.
HTML;

				break;

			case 'unsupported_grant_type':
				$description = <<< HTML
The mail service reported that it cannot grant your API application access to your account. Please make sure that you have create an API application under the correct service account and it's set up to serve a hosted web application (NOT an installed application or JavaScript application). Double check the redirection URLs you've set up in your API application so they match our documentation. Moreover, make sure you have filled in the API Client ID and API Secret Key correctly <strong>and</strong> that you have selected the correct scopes in your API application (per our documentation) before clicking the Authentication button again.
HTML;

				break;

			case 'invalid_scope':
				$description = <<< HTML
Google Mail reported that it does not understand the permissions your API application requested to be granted. Please make sure that you have create an API application under the correct service account and it's set up to serve a hosted web application (NOT an installed application or JavaScript application). Double check the redirection URLs you've set up in your API application so they match our documentation. Moreover, make sure you have filled in the API Client ID and API Secret Key correctly <strong>and</strong> that you have selected the correct scopes in your API application (per our documentation) before clicking the Authentication button again.
HTML;

				break;
		}

		if (!empty($errorDescription))
		{
			$description = $errorDescription;
		}

		return <<< HTML
<html lang="en-GB">
<head>
	<title>Mail Service Authentication Error $error</title>
</head>
<body>
	<h1>Mail Service Authentication Error <code>$error</code></h1>
	<p>
		$description
	</p>
</body>
</html>
HTML;
	}
}