304 lines
8.0 KiB
PHP
304 lines
8.0 KiB
PHP
<?php
|
|
|
|
/**
|
|
* @author Tassos Marinos <info@tassos.gr>
|
|
* @link https://www.tassos.gr
|
|
* @copyright Copyright © 2024 Tassos All Rights Reserved
|
|
* @license GNU GPLv3 <http://www.gnu.org/licenses/gpl.html> or later
|
|
*/
|
|
|
|
namespace NRFramework;
|
|
|
|
defined('_JEXEC') or die('Restricted access');
|
|
|
|
use NRFramework\Functions;
|
|
use NRFramework\URLHelper;
|
|
use Joomla\CMS\Factory;
|
|
use Joomla\CMS\Uri\Uri;
|
|
use Joomla\Filesystem\Path;
|
|
use Joomla\CMS\Language\Text;
|
|
|
|
/**
|
|
* Framework Emailer
|
|
*/
|
|
class Email
|
|
{
|
|
/**
|
|
* Indicates the last error
|
|
*
|
|
* @var string
|
|
*/
|
|
public $error;
|
|
|
|
/**
|
|
* Email Object
|
|
*
|
|
* @var email data to be sent
|
|
*/
|
|
private $email;
|
|
|
|
/**
|
|
* Required elements for a valid email object
|
|
*
|
|
* @var array
|
|
*/
|
|
private $requiredKeys = [
|
|
'from_email',
|
|
'from_name',
|
|
'recipient',
|
|
'subject',
|
|
'body'
|
|
];
|
|
|
|
/**
|
|
* Class constructor
|
|
*/
|
|
public function __construct($email)
|
|
{
|
|
$this->email = $email;
|
|
}
|
|
|
|
/**
|
|
* Validates Email Object
|
|
*
|
|
* @param array $email The email object
|
|
*
|
|
* @return boolean Returns true if the email object is valid
|
|
*/
|
|
public function validate()
|
|
{
|
|
// Validate email object
|
|
if (!$this->email || !is_array($this->email) || !count($this->email))
|
|
{
|
|
$this->setError('Invalid email object.');
|
|
return;
|
|
}
|
|
|
|
// Check for missing properties
|
|
foreach ($this->requiredKeys as $key)
|
|
{
|
|
if (!isset($this->email[$key]) || empty($this->email[$key]))
|
|
{
|
|
$this->setError("The $key field is either missing or invalid.");
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Validate recipient email addresses.
|
|
$this->email['recipient'] = Functions::makeArray($this->email['recipient']);
|
|
|
|
foreach ($this->email['recipient'] as $recipient)
|
|
{
|
|
if (!$this->validateEmailAddress($recipient))
|
|
{
|
|
$this->setError("Invalid recipient email address: $recipient");
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Validate sender email address
|
|
if (!$this->validateEmailAddress($this->email['from_email']))
|
|
{
|
|
$this->setError('Invalid sender email address: ' . $this->email['from_email']);
|
|
return;
|
|
}
|
|
|
|
$this->email['bcc'] = isset($this->email['bcc']) ? Functions::makeArray($this->email['bcc']) : [];
|
|
$this->email['cc'] = isset($this->email['cc']) ? Functions::makeArray($this->email['cc']) : [];
|
|
|
|
// Convert special HTML entities back to characters on non text-only properties.
|
|
// For instance, the subject line of an email is not parsed as HTML, it's just pure text.
|
|
// Because of this an HTML entity like & it will be displayed as encoded.
|
|
// To prevent this from happening we need decode the values.
|
|
$this->email['subject'] = htmlspecialchars_decode($this->email['subject']);
|
|
$this->email['from_name'] = htmlspecialchars_decode($this->email['from_name']);
|
|
$this->email['reply_to_name'] = htmlspecialchars_decode($this->email['reply_to_name']);
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Sending emails
|
|
*
|
|
* @param array $email The mail objecta
|
|
*
|
|
* @return mixed Returns true on success. Throws exeption on fail.
|
|
*/
|
|
public function send()
|
|
{
|
|
// Proceed only if Mail Sending is enabled.
|
|
if (!Factory::getConfig()->get('mailonline'))
|
|
{
|
|
$this->error = Text::_('NR_ERROR_EMAIL_IS_DISABLED');
|
|
return;
|
|
}
|
|
|
|
// Validate first the email object
|
|
if (!$this->validate($this->email))
|
|
{
|
|
return;
|
|
}
|
|
|
|
$email = $this->email;
|
|
$mailer = Factory::getMailer();
|
|
$mailer->CharSet = 'UTF-8';
|
|
$mailer->Encoding = 'quoted-printable';
|
|
|
|
// Email Sender
|
|
$mailer->setSender([
|
|
$email['from_email'],
|
|
$email['from_name']
|
|
]);
|
|
|
|
// Reply-to
|
|
if (isset($email['reply_to']) && !empty($email['reply_to']))
|
|
{
|
|
$name = (isset($email['reply_to_name']) && !empty($email['reply_to_name'])) ? $email['reply_to_name'] : '';
|
|
|
|
$reply_to_addresses = array_filter(array_map('trim', explode(',', $email['reply_to'])));
|
|
|
|
foreach($reply_to_addresses as $reply_to_address)
|
|
{
|
|
$mailer->addReplyTo($reply_to_address, $name);
|
|
}
|
|
}
|
|
|
|
// Convert all relative paths found in <a> and <img> elements to absolute URLs
|
|
$email['body'] = URLHelper::relativePathsToAbsoluteURLs($email['body']);
|
|
|
|
// Fix space characters displayed as ???? in old email clients like SquirrelMail.
|
|
// Ticket reference: https://smilemotive.teamwork.com/desk/tickets/96313487/messages
|
|
$specialSpace = [
|
|
"\xC2\xA0",
|
|
"\xE1\xA0\x8E",
|
|
"\xE2\x80\x80",
|
|
"\xE2\x80\x81",
|
|
"\xE2\x80\x82",
|
|
"\xE2\x80\x83",
|
|
"\xE2\x80\x84",
|
|
"\xE2\x80\x85",
|
|
"\xE2\x80\x86",
|
|
"\xE2\x80\x87",
|
|
"\xE2\x80\x88",
|
|
"\xE2\x80\x89",
|
|
"\xE2\x80\x8A",
|
|
"\xE2\x80\x8B",
|
|
"\xE2\x80\xAF",
|
|
"\xE2\x81\x9F",
|
|
"\xEF\xBB\xBF",
|
|
];
|
|
|
|
$email['body'] = str_replace($specialSpace, " ", $email['body']);
|
|
|
|
$mailer
|
|
->addRecipient($email['recipient'])
|
|
->isHTML(true)
|
|
->setSubject($email['subject'])
|
|
->setBody($email['body']);
|
|
|
|
$mailer->AltBody = strip_tags(str_ireplace(['<br />', '<br>', '<br/>'], "\r\n", $email['body']));
|
|
|
|
// Add CC
|
|
if (!empty($email['cc']))
|
|
{
|
|
$mailer->addCc($email['cc']);
|
|
}
|
|
|
|
// Add BCC
|
|
if (!empty($email['bcc']))
|
|
{
|
|
$mailer->addBcc($email['bcc']);
|
|
}
|
|
|
|
// Attachments
|
|
$attachments = $email['attachments'];
|
|
|
|
if (!empty($attachments))
|
|
{
|
|
if (!is_array($attachments))
|
|
{
|
|
$attachments = explode(',', $attachments);
|
|
}
|
|
|
|
// Validate Attachments
|
|
$attachments = array_filter(array_map('trim', $attachments));
|
|
|
|
foreach ($attachments as $attachment)
|
|
{
|
|
$file_path = $this->toRelativePath($attachment);
|
|
|
|
if (!is_file($file_path))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
$mailer->addAttachment($file_path);
|
|
}
|
|
}
|
|
|
|
// Send mail
|
|
$send = $mailer->Send();
|
|
|
|
if ($send !== true)
|
|
{
|
|
$this->setError($send->__toString());
|
|
return;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Set Class Error
|
|
*
|
|
* @param string $error The error message
|
|
*/
|
|
private function setError($error)
|
|
{
|
|
$this->error = 'Error sending email: ' . $error;
|
|
}
|
|
|
|
/**
|
|
* Removes all illegal characters and validates an email address
|
|
*
|
|
* @param string $email Email address string
|
|
*
|
|
* @return bool
|
|
*/
|
|
private function validateEmailAddress($email)
|
|
{
|
|
// If the email address contains an ampersand, throw an error
|
|
if (strpos($email, '&') !== false)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
$email = filter_var($email, FILTER_SANITIZE_EMAIL);
|
|
return filter_var($email, FILTER_VALIDATE_EMAIL);
|
|
}
|
|
|
|
/**
|
|
* Attempts to transform an absolute URL to path relative to the site's root.
|
|
*
|
|
* @param string $url
|
|
*
|
|
* @return string
|
|
*/
|
|
private function toRelativePath($url)
|
|
{
|
|
$needles = [
|
|
Uri::root(),
|
|
JPATH_SITE,
|
|
JPATH_ROOT
|
|
];
|
|
|
|
$path = str_replace($needles, '', $url);
|
|
|
|
$path = Path::clean($path);
|
|
|
|
// Relative paths should not start with a slash.
|
|
$path = ltrim($path, '/');
|
|
|
|
return $path;
|
|
}
|
|
} |