| Current Path : /var/www/consult-e-syn/public_html/plugins/ats/mailfetch/ |
| 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;
}
}