| Current Path : /var/www/consult-e-syn/public_html/plugins/ats/postemail/ |
| Current File : /var/www/consult-e-syn/public_html/plugins/ats/postemail/postemail.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\AddonEmails;
use Akeeba\TicketSystem\Admin\Helper\Email;
use Akeeba\TicketSystem\Admin\Helper\Html;
use Akeeba\TicketSystem\Admin\Helper\Permissions;
use Akeeba\TicketSystem\Admin\Helper\Subscriptions;
use Akeeba\TicketSystem\Admin\Model\Attachments;
use Akeeba\TicketSystem\Admin\Model\Posts;
use Akeeba\TicketSystem\Admin\Model\Tickets;
use Akeeba\TicketSystem\Site\Helper\Html2Text;
use FOF40\Model\DataModel\Collection as DataCollection;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Mail\MailHelper as JMailHelper;
use Joomla\CMS\Plugin\CMSPlugin;
use Joomla\CMS\Router\Route;
use Joomla\CMS\User\User as JUser;
use Joomla\Registry\Registry as JRegistry;
class plgAtsPostemail extends CMSPlugin
{
/** @var \FOF40\Container\Container FOF Container */
private $container;
/**
* Public constructor
*
* @param object $subject The object to observe
* @param array $config Configuration parameters to the plugin
*/
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);
}
$version_php = JPATH_ADMINISTRATOR . '/components/com_ats/version.php';
if (!defined('ATS_VERSION') && @is_file($version_php))
{
require_once $version_php;
}
$this->loadLanguage();
$this->container = FOF40\Container\Container::getInstance('com_ats');
}
/**
* This handles the onATSPost event which is triggered every time a post is
* created or edited.
*
* @param array $info An indexed array with post info. The post key contains an AtsTablePost object.
*
* @return void
*/
public function onATSPost($info)
{
// Only send notifications for NEW posts, or posts edited by a non-administrator
$sendEmail = false;
if ($info['new'])
{
// New post; send email
$sendEmail = true;
}
else
{
// Modified post. Let's think...
$modified_by = $info['post']->modified_by;
if ($modified_by <= 0)
{
// Not modified / modified by invalid user - no email
$sendEmail = false;
}
else
{
$isAdmin = Permissions::isManager($info['ticket']->catid, $modified_by);
if (!$isAdmin)
{
$sendEmail = true;
}
}
}
if (!$sendEmail)
{
return;
}
// Extract the post and ticket objects
/** @var Posts $post */
$post = $info['post'];
/** @var Tickets $ticket */
$ticket = $info['ticket'];
// Is this a new ticket?
/** @var Posts $firstPost */
$firstPost = $this->getFirstPost($ticket);
$isNewTicket = $firstPost->getId() == $post->getId();
// Let's get the user ID's of all users who are managers of ATS or the specific category
$managers = $this->_getManagersForATSCategory($ticket->catid);
$skipNotAssigned = $this->container->params->get('assigned_noemail', 0);
if (!empty($managers))
{
foreach ($managers as $manager)
{
// Make sure we are not sending an email to the user who posted
if ($manager == $post->created_by)
{
continue;
}
// Should I notify only the assigned manager?
if ($skipNotAssigned)
{
// Ticket assigned, let's skip everyone else!
if ($info['ticket']->assigned_to && $info['ticket']->assigned_to != $manager)
{
continue;
}
}
// Send email to the manager
$this->_sendEmailToUser($manager, $post, $ticket, 'manager', $isNewTicket);
}
}
// Send email to the ticket owner, unless it's a reply sent by himself.
// New ticket notifications will always be sent.
if (($ticket->created_by != $post->created_by) || $isNewTicket)
{
$this->_sendEmailToUser($ticket->created_by, $post, $ticket, 'owner', $isNewTicket);
}
}
/**
* Event fired when a ticket is assigned to someone. ATS will notify assigned user (unless they assigned
* a ticket to themselves
*
* @param Tickets $ticket Assigned ticket
*
* @return bool Is everything alright?
*/
public function onATSassign($ticket)
{
// Current user is the one assigned to, it's useless to notify myself
if ($ticket->assigned_to == Permissions::getUser()->id)
{
return true;
}
try
{
$mailer = Email::getMailer();
}
catch (Exception $e)
{
// JMail died on us
return true;
}
[$subject, $body,] = Email::loadEmailTemplateFromDB('manager-assignedticket');
$sitename = $this->container->platform->getConfig()->get('sitename');
$user = Permissions::getUser($ticket->assigned_to);
try
{
$mailer->addRecipient($user->email, $user->name);
}
catch (Exception $e)
{
// JMail died on us
return true;
}
// Add any add-on emails
$addOnEmails = AddonEmails::getAddonEmails($user->id);
$addOnEmails = AddonEmails::filterAddonEmails($addOnEmails, $user->id);
if (is_array($addOnEmails) && !empty($addOnEmails))
{
foreach ($addOnEmails as $email => $name)
{
try
{
$mailer->addCc($email, $name);
}
catch (Exception $e)
{
}
}
}
$category = $ticket->joomla_category;
$ticketURL = Route::link('site', 'index.php?option=com_ats&view=ticket&id=' . $ticket->ats_ticket_id, false, Route::TLS_IGNORE, true);
//Grab first post, so we can use its text inside the email
/** @var Posts $post */
$post = $ticket->posts->first();
$attachmentURL = '';
if ($post->ats_attachment_id)
{
/** @var \Akeeba\TicketSystem\Site\Model\Attachments $attachment */
$attachment = $this->container->factory->model('Attachments')->tmpInstance();
$attachment->find($post->ats_attachment_id[0]);
$attFilename = Attachments::getAttachmentsDirectory() . '/' . $attachment->mangled_filename;
$realName = $attachment->original_filename;
if (@file_exists($attFilename))
{
$attSize = @filesize($attFilename);
if ($attSize < 2097152)
{
// Attach the file
try
{
$mailer->addAttachment($attFilename, $realName, 'base64', $attachment->mime_type);
}
catch (Exception $e)
{
// JMail died on us
}
}
else
{
// Create a link to the attachment, for attachments over 2Mb
$attachmentURL = Route::link('site', 'index.php?option=com_ats&view=attachment&task=read&format=raw&id=' . $attachment->ats_attachment_id, true, Route::TLS_IGNORE, true);
}
}
}
$mailInfo = [
'id' => $ticket->ats_ticket_id,
'title' => $ticket->title,
'attachment' => empty($attachmentURL) ? '' : Text::sprintf('PLG_ATS_POSTEMAIL_ATTACHMENT_LINK', $attachmentURL, $realName),
'url' => $ticketURL,
'catname' => $category->title,
'text' => $post->content_html,
'sitename' => $sitename,
'user_name' => $user->name,
];
$makePlaintext = $this->params->get('makePlaintext', 1);
try
{
Email::parseTemplate($body, $subject, $mailInfo, $mailer);
// Create a plain text representation of the email body if we are asked to
if ($makePlaintext)
{
$html2Text = new Html2Text($mailer->Body);
$mailer->AltBody = $html2Text->getText();
}
// Send the email
$mailer->Send();
unset($mailer);
}
catch (Exception $e)
{
// JMail died on us
return true;
}
return true;
}
/**
* Gets all the user IDs registered as managers for a specific ATS category
*
* @param int $catid
*
* @return array
*/
private function _getManagersForATSCategory($catid)
{
$managers = Permissions::getManagers($catid);
/** @var \Akeeba\TicketSystem\Site\Model\Categories $category */
$category = $this->container->factory->model('Categories')->tmpInstance();
$category->load($catid);
$params = new JRegistry($category->params);
if (!is_object($params))
{
$notify = ['all'];
$exclude = [];
}
else
{
$notify = $params->get('notify_managers', ['all']);
$exclude = $params->get('exclude_managers', []);
}
$whitelist = [];
$blacklist = [];
// Only notify the managers selected by the site administrators if they are, indeed, managers of this category.
foreach ($managers as $manager)
{
if (in_array('all', $notify) || in_array($manager->id, $notify))
{
$whitelist[] = $manager->id;
}
}
// Remove managers from the whitelist array
foreach ($managers as $manager)
{
if (in_array('all', $exclude) || in_array($manager->id, $exclude))
{
$blacklist[] = $manager->id;
}
}
$ret = array_diff($whitelist, $blacklist);
return $ret;
}
/**
* Sends a new post email notification
*
* @param int $user_id Recipient's user ID
* @param Posts $post
* @param Tickets $ticket
* @param string $toWhom One of 'manager', 'owner' or 'subscriber'.
* Determines the subject and post body template.
* @param null|bool $isNewTicket Is this a new ticket? NULL to auto-determine.
*
* @return bool Is the email correctly sent?
*/
private function _sendEmailToUser($user_id, $post, $ticket, $toWhom = 'manager', $isNewTicket = null)
{
$isCLI = $this->container->platform->isCli();
if ($isCLI)
{
$siteURL = $this->container->params->get('siteurl', 'http://www.example.com');
}
// Extract the useful information
$userReceipient = Permissions::getUser($user_id);
if ($post->created_by == -1)
{
$userPoster = clone $this->container->platform->getUser();
$userPoster->username = 'system';
$userPoster->name = Text::_('COM_ATS_CLI_SYSTEMUSERLABEL');
}
else
{
$userPoster = Permissions::getUser($post->created_by);
}
try
{
$mailer = Email::getMailer();
}
catch (Exception $e)
{
// JMail died on us
return true;
}
// If the category specifies a category_email parameter,
// set the Reply-To address to it.
/** @var \Akeeba\TicketSystem\Site\Model\Categories $category */
$category = $this->container->factory->model('Categories')->tmpInstance();
$category->find($ticket->catid);
$params = new JRegistry();
$params->loadString($category->params, 'JSON');
$category_email = $params->get('category_email', '');
$category_email = trim($category_email);
if (!empty($category_email))
{
$mailfrom = $category_email;
$conf = $this->container->platform->getConfig();
$fromname = $conf->get('fromname');
$didWeAddAReplyToAddress = false;
try
{
$mailer->addReplyTo([JMailHelper::cleanLine($mailfrom), JMailHelper::cleanLine($fromname)]);
$didWeAddAReplyToAddress = true;
}
catch (Exception $e)
{
/**
* It is very possible that we have Joomla! 3.5 which doesn't let us pass an array, unlike Joomla! 3.4
* or earlier. We have to assume that a failure here means we should retry without using an array.
*/
}
if (!$didWeAddAReplyToAddress)
{
try
{
$mailer->addReplyTo(JMailHelper::cleanLine($mailfrom), JMailHelper::cleanLine($fromname));
}
catch (Exception $e)
{
// JMail died on us
return true;
}
}
}
try
{
$mailer->addRecipient($userReceipient->email, $userReceipient->name);
}
catch (Exception $e)
{
// JMail died on us
return true;
}
// Add any add-on emails
$addOnEmails = AddonEmails::getAddonEmails($userReceipient->id);
$addOnEmails = AddonEmails::filterAddonEmails($addOnEmails, $userReceipient->id);
if (is_array($addOnEmails) && !empty($addOnEmails))
{
foreach ($addOnEmails as $email => $name)
{
try
{
$mailer->addCC($email, $name);
}
catch (Exception $e)
{
}
}
}
if (is_null($isNewTicket))
{
/** @var Posts $firstPost */
$firstPost = $this->getFirstPost($ticket);
$isNewTicket = $firstPost->getId() == $post->getId();
}
// Get default body and subject
[$template, $subject] = $this->_getEmailTemplate($toWhom, $isNewTicket, $ticket->public);
// I have no body and subject, this means that the webmaster doesn't want to send any emails,
// so let's stop here
if (!$template && !$subject)
{
return true;
}
// Set mail priority using phpmailer Priority variable and setting custom headers.
// X-Priority header is already set by phpmailer
// http://www.php.net/manual/en/function.mail.php#91058
try
{
if ($ticket->priority == 1)
{
$mailer->Priority = 2;
$head_priority = 'High';
}
elseif ($ticket->priority == 5)
{
$mailer->Priority = 3;
$head_priority = 'Normal';
}
else
{
$mailer->Priority = 5;
$head_priority = 'Low';
}
}
catch (Exception $e)
{
// JMail died on us
return true;
}
try
{
/**
* So, the X-MSMail-Priority is required by Microsoft Outlook since it seems oblivious to the standard
* Importance header. However, SpamAssassin thinks that the presence of this header without X-MimeOLE means
* that your mail is spam. As a result we have to ignore users of Microsoft Outlook to make SpamAssassin not
* treat our legit mail as spam. If we don't, eventually all of the mails sent from the domain are marked as
* spam.
*
* As a small consolation prize, newer versions of Microsoft Outlook seem to have figured our how to honor
* RFC4021 in that respect. So even though we ignore users of legacy versions of Microsoft Outlook (which
* is broken) to please SpamAssassin (whihc is also broken) we can at least rest assured that users of a
* modern version of Microsoft Outlook will see the correct priority. Oh, well...
*/
// $mailer->addCustomHeader('X-MSMail-Priority: ' . $head_priority);
$mailer->addCustomHeader('Importance: ' . $head_priority);
}
catch (Exception $e)
{
// JMail died on us
return true;
}
// Attach files if less than 2MB (with addAttachment($attachFile)), or set up a link to them
$attachmentURLs = [];
if ($post->ats_attachment_id)
{
/** @var \Akeeba\TicketSystem\Site\Model\Attachments $attachment */
$attachment = $this->container->factory->model('Attachments')->tmpInstance();
foreach ($post->ats_attachment_id as $id_attachment)
{
// Post with no attachment (id_attachment == 0)
if (!$id_attachment)
{
continue;
}
$attachment->find($id_attachment);
$attFilename = Attachments::getAttachmentsDirectory() . '/' . $attachment->mangled_filename;
$realName = $attachment->original_filename;
if (!@file_exists($attFilename))
{
continue;
}
$attSize = @filesize($attFilename);
if ($attSize < 2097152 && count($post->ats_attachment_id) == 1)
{
// Attach the file
try
{
$mailer->addAttachment($attFilename, $realName, 'base64', $attachment->mime_type);
}
catch (Exception $e)
{
// JMail died on us
}
}
else
{
// Create a link to the attachment, for attachments over 2Mb or for multiple attachments
$attachmentURLs[$realName] = Route::link('site', 'index.php?option=com_ats&view=attachment&task=read&format=raw&id=' . $attachment->ats_attachment_id, false, Route::TLS_IGNORE, true);
}
}
}
// Get a link to the new post
$postURL = Route::link('site', 'index.php?option=com_ats&view=ticket&id=' . $ticket->ats_ticket_id, false, Route::TLS_IGNORE, true);
$postURL .= '#p' . $post->ats_post_id;
// Get the body of the message, based on overridable template files
$sitename = $this->container->platform->getConfig()->get('sitename');
$attachmentToken = [];
if ($attachmentURLs)
{
foreach ($attachmentURLs as $realName => $attachmentURL)
{
$attachmentToken[] = Text::sprintf('PLG_ATS_POSTEMAIL_ATTACHMENT_LINK', $attachmentURL, $realName);
}
}
$attachmentToken = implode('<br/>', $attachmentToken);
$mailInfo = [
'id' => $ticket->ats_ticket_id,
'title' => $ticket->title,
'url' => $postURL,
'attachment' => $attachmentToken,
'poster_username' => $userPoster->username,
'poster_name' => $userPoster->name,
'user_username' => $userReceipient->username,
'user_name' => $userReceipient->name,
'text' => $post->content_html,
'catname' => $category->title,
'sitename' => $sitename,
'origin' => $post->origin,
'avatar' => Html::getAvatarURL($userPoster, 64),
'assigned_to' => $ticket->assigned_to ? Permissions::getUser($ticket->assigned_to)->name : Text::_('COM_ATS_TICKETS_UNASSIGNED'),
'modified_by' => $ticket->modified_by ? Permissions::getUser($ticket->modified_by)->name : '',
];
$mailInfo['subscriptions'] = '';
// Import the ATS plugins. Used everywhere below, basically.
if ($this->container->platform->isCli())
{
$allowedPluginsInCli = $this->container->platform->isAllowPluginsInCli();
$this->container->platform->setAllowPluginsInCli(true);
}
$this->container->platform->importPlugin('ats');
if ($this->container->platform->isCli())
{
$this->container->platform->setAllowPluginsInCli($allowedPluginsInCli);
}
// Add the user's subscriptions if there is a subscriptions component integration plugin
if (Subscriptions::hasSubscriptionsComponent())
{
$subs = Subscriptions::getSubscriptionsList($userPoster);
$mailInfo['subscriptions'] = implode(', ', $subs->active);
}
// Add GMail Go-To View Action markup, see https://developers.google.com/gmail/markup/reference/go-to-action#view_action
$gmailActions = <<< HTML
<script type="application/ld+json">
{
"@context": "http://schema.org",
"@type": "EmailMessage",
"potentialAction": {
"@type": "ViewAction",
"url": "$postURL",
"name": "View ticket"
},
"description": "Open ticket #{$ticket->ats_ticket_id} in your browser"
}
</script>
HTML;
/**
* Inject the GMail action string WITHIN the <body> tag, otherwise some email clients will complain about
* broken HTML in the message.
*
* If we can't find the <body> tag let's put it on top and hope for the best
*/
$template = preg_replace('#<body[^>]*>#i', '$0' . $gmailActions, $template, -1, $count);
if ($count < 1)
{
$template = $gmailActions . $template;
}
/**
* Call other ATS plugins to modify the email.
*
* This is used, for example, to inject the reply line when the Reply by Email feature is enabled.
*/
$this->container->platform->runPlugins('onATSSendEmail', [
&$subject, &$template, $mailer, &$mailInfo, $toWhom,
]);
try
{
Email::parseTemplate($template, $subject, $mailInfo, $mailer);
// Create a plain text representation of the email body if we are asked to
if ($this->params->get('makePlaintext', 1) == 1)
{
$html2Text = new Html2Text($mailer->Body);
$mailer->AltBody = $html2Text->getText();
}
// Send the email
$ret = $mailer->Send();
unset($mailer);
return $ret;
}
catch (Exception $e)
{
// JMail died on us
return true;
}
}
/**
* Gets an email template from the database (preferred), from the overrides
* directory (deprecated) or from the translation keys (not encouraged).
*
* @param string $tpl The template type to fetch, e.g manager or owner
* @param bool $isNewTicket Is this a new ticket (true) or a reply (false)?
* @param bool $isPublic Is this a public (true) or a private (false) ticket?
*
* @return array An array containing the subject and template text
*/
private function _getEmailTemplate($tpl = 'manager', $isNewTicket = true, $isPublic = true)
{
// Get status variables
$tmplFile = '';
$templateText = '';
$subject = '';
$loadLanguage = null;
// Get status variables
$newold = $isNewTicket ? 'NEW' : 'OLD';
$status = $isPublic ? 'PUBLIC' : 'PRIVATE';
$key = strtolower("$tpl-$status-$newold");
// If I'm under ATS PRO, I will only load the template from the DB
if (ATS_PRO)
{
[$templateText, $subject, $loadLanguage] = Email::loadEmailTemplateFromDB($key);
}
// Otherwise I'll load it from the filesystem
else
{
// Get default body
$loadLanguage = null;
$basePath = dirname(__FILE__) . '/templates/';
$jLang = \Joomla\CMS\Factory::getLanguage();
$userLang = Permissions::getUser()->getParam('language', '');
$languages = [
$userLang,
$jLang->getTag(),
$jLang->getDefault(),
'en-GB',
'*',
];
foreach ($languages as $lang)
{
if (empty($lang))
{
continue;
}
if (!empty($loadLanguage))
{
continue;
}
$filename = "$lang/$tpl.tpl";
if (file_exists($basePath . 'overrides/' . $filename))
{
$loadLanguage = $lang;
$tmplFile = $basePath . 'overrides/' . $filename;
break;
}
elseif (file_exists($basePath . 'default/' . $filename))
{
$loadLanguage = $lang;
$tmplFile = $basePath . 'default/' . $filename;
break;
}
}
// Load the template text
if ($tmplFile && JFile::exists($tmplFile))
{
$templateText = @file_get_contents($tmplFile);
if (empty($templateText))
{
$templateText = @file_get_contents($tmplFile);
}
}
}
return [$subject, $templateText];
}
private function getFirstPost(Tickets $ticket)
{
/** @var DataCollection|null $posts */
$posts = $ticket->posts;
if (!is_object($posts) || !($posts instanceof DataCollection))
{
/** @var Posts $postModel */
$postModel = $this->container->factory->model('Posts')->tmpInstance();
$postModel->ats_ticket_id($ticket->getId());
$postModel->orderBy('ats_ticket_id', 'ASC');
$postModel->with([]);
$posts = $postModel->get(true);
}
return $posts->first();
}
}