| Current Path : /var/www/cesa.co.za/php/includes/ |
| Current File : /var/www/cesa.co.za/php/includes/maill.php |
<?php
include_once($_SERVER['DOCUMENT_ROOT'] . '/cfg/config.php');
// Load Composer autoloader for PHPMailer
if (file_exists($_SERVER['DOCUMENT_ROOT'] . '/vendor/autoload.php')) {
// echo 'Loading PHPMailer' . "\r\n";
include_once($_SERVER['DOCUMENT_ROOT'] . '/vendor/autoload.php');
} else {
// echo 'PHPMailer autoloader not found at: ' . $_SERVER['DOCUMENT_ROOT'] . '/vendor/autoload.php' . "\r\n";
}
include_once($_SERVER['DOCUMENT_ROOT'] . "/php/includes/ErrorLog.php");
include_once($_SERVER['DOCUMENT_ROOT'] . "/php/includes/Mailer.php");
include_once($_SERVER['DOCUMENT_ROOT'] . "/php/includes/sqlFunctions.php");
include_once($_SERVER['DOCUMENT_ROOT'] . "/php/includes/Utils.php");
include_once($_SERVER['DOCUMENT_ROOT'] . "/php/includes/CceUploadedFileService.php");
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\Exception as phpmailerException;
if (!function_exists("maill_log_send_failure")) {
function maill_log_send_failure($subject, $arrto, $smtp_host, $smtp_port, $smtp_user, $mail, $mailError, $config)
{
if (!class_exists('ErrorLog')) {
return;
}
$errorMessage = 'Error on CESA mail send with Subject: ' . $subject . ' To: ' . print_r($arrto, true)
. ' - ' . $smtp_host . ' - ' . $smtp_port . ' - ' . $smtp_user
. ' Error details: ' . (is_object($mail) ? $mail->ErrorInfo : '')
. ' - ' . $mailError->getMessage();
$notify = empty($config['error_mail']);
ErrorLog::logError($errorMessage, $notify);
}
}
if (!function_exists("maill_stage_attachment")) {
function maill_stage_attachment($attachmentPath, $attachmentName, CceUploadedFileService $fileService, &$stagedTempFiles)
{
if (!is_string($attachmentPath) || $attachmentPath === '') {
return null;
}
$displayName = $attachmentName !== null && $attachmentName !== ''
? $attachmentName
: basename($attachmentPath);
if (!is_file($attachmentPath)) {
$documentRoot = isset($_SERVER['DOCUMENT_ROOT']) ? (string) $_SERVER['DOCUMENT_ROOT'] : '';
$resolved = $fileService->resolveFilePath(
$documentRoot,
CceUploadedFileService::STORAGE_MSGATTACH,
basename($attachmentPath)
);
if ($resolved !== null) {
$attachmentPath = $resolved;
}
}
if (!is_file($attachmentPath)) {
return null;
}
$staged = $fileService->stageFileForMail($attachmentPath, $displayName);
if ($staged === null) {
return null;
}
if (!empty($staged['temp'])) {
$stagedTempFiles[] = $staged['path'];
}
return $staged;
}
}
if (!function_exists("maill")) {
function maill($to, $subject, $message, $uploadfile1 = null, $config = array())
{
global $sqlf, $env; //, $smtp_host, $smtp_port, $smtp_user, $smtp_pass;
// SMTP Configuration (Office365: From must match authenticated mailbox)
$smtp_host = 'smtp.office365.com';
$smtp_port = 587;
$smtp_user = 'no-reply@cesa.co.za';
$smtp_pass = 'Q3b2025#!';
if (isset($config['headers_raw']) && $config['headers_raw'] != '') {
$config = array_merge($config, Mailer::extractHeaders($config['headers_raw']));
}
// var_dump($config);
// exit;
if (!isset($config['from-address']) || strlen($config['from-address']) <= 0) {
$config['from-address'] = $smtp_user;
}
// Reply-To may be any @cesa.co.za address; default to the requested from-address
if (!isset($config['reply-to-address']) || strlen($config['reply-to-address']) <= 0) {
$config['reply-to-address'] = $config['from-address'];
}
// Ensure all SCE mails are CC'd to accounts@cesa.co.za
$sceAccountsCc = 'accounts@cesa.co.za';
$fromAddress = isset($config['from-address']) ? (string) $config['from-address'] : '';
$fromName = isset($config['from-name']) ? (string) $config['from-name'] : '';
$replyToAddress = isset($config['reply-to-address']) ? (string) $config['reply-to-address'] : '';
$isSceMail =
(strcasecmp($fromAddress, 'sce@cesa.co.za') === 0)
|| (stripos($fromName, 'CESA SCE') !== false)
|| (strcasecmp($replyToAddress, 'melissa@cesa.co.za') === 0);
if ($isSceMail) {
if (!isset($config['cc']) || !is_array($config['cc'])) {
$config['cc'] = [];
}
$alreadyCc = false;
foreach ($config['cc'] as $cc) {
if (is_string($cc) && strcasecmp(trim($cc), $sceAccountsCc) === 0) {
$alreadyCc = true;
break;
}
}
if (!$alreadyCc) {
$config['cc'][] = $sceAccountsCc;
}
}
// ErrorLog::logInfo('Message send started: ' . $config['reply-to-address'] . ' - ' . $subject . ' - ' . $to, $sqlf);
// Debug: Log received config values
ErrorLog::logInfo('maill() received config - from-name: "' . (isset($config['from-name']) ? $config['from-name'] : 'NOT SET') . '", from-address: "' . (isset($config['from-address']) ? $config['from-address'] : 'NOT SET') . '", isHTML: ' . (isset($config['isHTML']) ? ($config['isHTML'] ? 'true' : 'false') : 'NOT SET'));
// Check if this is a bulk mail (from mailbox processing)
$isBulkMail = isset($config['source']) && strpos($config['source'], 'mailbox-') === 0;
if (!isset($config['from-name']) || strlen($config['from-name']) <= 0) {
// For bulk mails, don't use default - the sender's name should have been extracted
// For other mails, use the default
if (!$isBulkMail) {
$config['from-name'] = 'BCE CESAnet';
ErrorLog::logInfo('maill() WARNING: from-name was not set or empty, using default: "' . $config['from-name'] . '"');
} else {
ErrorLog::logInfo('maill() WARNING: from-name was not set or empty for bulk mail - sender name may not have been extracted');
}
} else {
ErrorLog::logInfo('maill() Using from-name: "' . $config['from-name'] . '"');
}
if (!isset($config['isHTML'])) {
$config['isHTML'] = false;
} else {
// Ensure isHTML is boolean
$config['isHTML'] = (bool)$config['isHTML'];
}
// Debug: Log isHTML status
ErrorLog::logInfo('maill() isHTML set to: ' . ($config['isHTML'] ? 'true' : 'false') . ' (type: ' . gettype($config['isHTML']) . ')');
if (!isset($config['error_mail'])) {
$config['error_mail'] = false;
}
$mailError = false;
$stagedTempFiles = array();
$fileService = new CceUploadedFileService();
$arrto = array();
$filePath = '';
try {
// echo 'Please ignore debug info: ' . $smtp_host . ' - ' . $smtp_port . ' - ' . $smtp_user . '<br>';
// exit;
$mail = new PHPMailer(true);
$mail->isSMTP(); // use SMTP
// $mail->SMTPDebug = true;
// $mail->isMail(); // use PHP Mail function
$mail->Host = $smtp_host;
$mail->Port = $smtp_port;
$mail->SMTPSecure = 'tls';
$mail->SMTPAuth = true;
$mail->Username = $smtp_user;
$mail->Password = $smtp_pass;
$mail->Timeout = 10; // Set timeout to 10 seconds to prevent hangs
$mail->SMTPOptions = array(
'ssl' => array(
'verify_peer' => false,
'verify_peer_name' => false,
'allow_self_signed' => true
)
);
// Office365: From is always no-reply@cesa.co.za; Reply-To carries the logical sender
$fromAddressToUse = $smtp_user;
if ($config['error_mail']) {
$mail->Host = 't1000.igotafrica.com';
$mail->Username = 'site-errors@igot.africa';
$mail->Password = 'CmKHkcjb8NX!';
$fromAddressToUse = 'site-errors@igot.africa';
}
// Set FromName (display name) - ensure it's trimmed, cleaned, and not empty
// The From address will always be the SMTP user, but FromName can be customized
$fromNameToUse = isset($config['from-name']) ? trim($config['from-name']) : 'CESA';
// Remove any quotes or problematic characters that might cause display issues
$fromNameToUse = str_replace(['"', "'", "\n", "\r"], '', $fromNameToUse);
$fromNameToUse = trim($fromNameToUse);
// Check if this is a bulk mail (from mailbox processing) - use the same check as above
$isBulkMail = isset($config['source']) && strpos($config['source'], 'mailbox-') === 0;
if (empty($fromNameToUse)) {
// For bulk mails, don't use default - the sender's name should have been extracted
// For other mails, use the default
if (!$isBulkMail) {
$fromNameToUse = 'BCE CESAnet';
ErrorLog::logInfo('maill() WARNING: from-name was empty after cleaning, using default');
} else {
ErrorLog::logInfo('maill() WARNING: from-name was empty after cleaning for bulk mail - sender name may not have been extracted');
// For bulk mails, if no name is available, use empty string (PHPMailer will handle it)
$fromNameToUse = '';
}
}
// Use setFrom() to set both address (SMTP user) and name (custom display name) together
// This ensures PHPMailer properly encodes the display name in the email headers
$mail->setFrom($fromAddressToUse, $fromNameToUse);
// Debug: Log what we're actually setting on PHPMailer
ErrorLog::logInfo('maill() PHPMailer setFrom() - From: "' . $mail->From . '", FromName: "' . $mail->FromName . '"');
ErrorLog::logInfo('maill() From address (SMTP user): "' . $fromAddressToUse . '", FromName (display): "' . $fromNameToUse . '"');
//Only one reply to address is allowed
$mail->clearReplyTos();
// Use the same cleaned from-name for reply-to to ensure consistency
$replyToAddress = Mailer::normalizeEmailAddress($config['reply-to-address']);
if ($replyToAddress !== '') {
$mail->addReplyTo($replyToAddress, $fromNameToUse);
$config['reply-to-address'] = $replyToAddress;
}
if (isset($config['i-cal']) && strlen($config['i-cal']) > 0) {
$mail->Ical = $config['i-cal'];
$filePath = sys_get_temp_dir() . '/invite' . time() . '.ics';
stringToFile($filePath, $config['i-cal']);
$mail->addAttachment($filePath);
}
// $mail->SMTPDebug = 3;
// $mail->Debugoutput = function ($str, $level) {
// echo "debug level $level; message: $str";
// }; //$mail->Debugoutput = 'echo';
$to = Mailer::repairSouthAfricanTldCommaTypos($to);
$arrto = Mailer::parseRecipientList($to);
$validRecipients = array();
$mail->clearAddresses();
if (count($arrto) <= 0) {
ErrorLog::logError('No recipients found for mail: ' . $subject);
return false;
}
for ($ito = 0; $ito < count($arrto); $ito++) {
if (
strtolower(trim($arrto[$ito])) == 'not applicable' ||
strtolower(trim($arrto[$ito])) == 'n/a' ||
strtolower(trim($arrto[$ito])) == 'na'
) {
continue;
}
$toName = '';
$normalized = Mailer::normalizeEmailAddress($arrto[$ito], $toName);
if ($normalized === '' || !Mailer::isValidEmailAddress($normalized)) {
ErrorLog::logInfo('maill() skipped invalid/empty recipient: "' . $arrto[$ito] . '" for subject: ' . $subject);
continue;
}
$validRecipients[] = $normalized;
$mail->addAddress($normalized, $toName);
} //for
$arrto = $validRecipients;
if (count($arrto) <= 0) {
ErrorLog::logError('No valid recipients found for mail: ' . $subject);
return false;
}
$mail->Subject = $subject;
$mail->IsHtml($config['isHTML']);
// Set charset from config or default to UTF-8
$mail->CharSet = isset($config['charset']) ? $config['charset'] : 'UTF-8';
// Handle embedded images before setting the message body
if ($config['isHTML'] && isset($config['embedded_images']) && is_array($config['embedded_images'])) {
foreach ($config['embedded_images'] as $embeddedImage) {
if (isset($embeddedImage['cid']) && isset($embeddedImage['path']) && file_exists($embeddedImage['path'])) {
try {
$contentType = isset($embeddedImage['content_type']) ? $embeddedImage['content_type'] : 'image/jpeg';
$mail->addEmbeddedImage($embeddedImage['path'], $embeddedImage['cid'], basename($embeddedImage['path']), 'base64', $contentType);
ErrorLog::logInfo('maill: Added embedded image with CID: ' . $embeddedImage['cid'] . ' from path: ' . $embeddedImage['path']);
} catch (Exception $imgEx) {
ErrorLog::logError('maill: Failed to add embedded image: ' . $imgEx->getMessage());
}
}
}
}
// Ensure message is properly set - don't corrupt HTML by appending text
if ($config['isHTML']) {
// For HTML emails, set Body as HTML and create a plain text AltBody
$mail->Body = $message;
// Create a plain text version for email clients that don't support HTML
$mail->AltBody = cleanHTMLMessageText($message);
// Debug logging
ErrorLog::logInfo('maill: Sending HTML email - Subject: ' . $subject . ', isHTML: true, content-type: ' . (isset($config['content-type']) ? $config['content-type'] : 'not set')
. ', Body length: ' . strlen($message) . ', HTML message preview: ' . substr($message, 0, 300) . ', embedded images: ' . (isset($config['embedded_images']) ? count($config['embedded_images']) : 0));
// Verify HTML structure is intact
if (stripos($message, '<html') === false && stripos($message, '<body') === false) {
ErrorLog::logInfo('maill() WARNING: HTML message does not appear to have proper HTML structure!');
}
} else {
// For plain text emails
$mail->Body = $message;
ErrorLog::logInfo('maill: Sending plain text email - Subject: ' . $subject . ', isHTML: false, content-type: ' . (isset($config['content-type']) ? $config['content-type'] : 'not set')
. ', Body length: ' . strlen($message));
}
if (!is_null($uploadfile1) && !empty($uploadfile1)) {
$stagedUpload = maill_stage_attachment($uploadfile1, basename($uploadfile1), $fileService, $stagedTempFiles);
if ($stagedUpload === null) {
throw new phpmailerException('Could not access file: ' . $uploadfile1);
}
$mail->AddAttachment($stagedUpload['path'], $stagedUpload['name']);
}
if (isset($config['attachments']) && is_array($config['attachments']) && count($config['attachments']) > 0) {
foreach ($config['attachments'] as $attachment) {
if (is_array($attachment) && isset($attachment['path'])) {
$attachmentPath = $attachment['path'];
$attachmentName = isset($attachment['name']) ? $attachment['name'] : basename($attachmentPath);
} else {
$attachmentPath = $attachment;
$attachmentName = basename((string) $attachmentPath);
}
$stagedAttachment = maill_stage_attachment($attachmentPath, $attachmentName, $fileService, $stagedTempFiles);
if ($stagedAttachment === null) {
throw new phpmailerException('Could not access file: ' . $attachmentPath);
}
$mail->AddAttachment($stagedAttachment['path'], $stagedAttachment['name']);
}
}
if (isset($config['cc']) && is_array($config['cc']) && count($config['cc']) > 0) {
foreach ($config['cc'] as $cc) {
$ccSanitized = Mailer::normalizeEmailAddress($cc);
if ($ccSanitized !== '' && Mailer::isValidEmailAddress($ccSanitized)) {
$mail->AddCC($ccSanitized);
}
}
}
if (isset($config['bcc']) && is_array($config['bcc']) && count($config['bcc']) > 0) {
foreach ($config['bcc'] as $bcc) {
$bccSanitized = Mailer::normalizeEmailAddress($bcc);
if ($bccSanitized !== '' && Mailer::isValidEmailAddress($bccSanitized)) {
$mail->AddBCC($bccSanitized);
}
}
}
if (isset($config['reply-to-address']) && strlen($config['reply-to-address']) > 0) {
$replyToSanitized = Mailer::normalizeEmailAddress($config['reply-to-address']);
if ($replyToSanitized !== '' && Mailer::isValidEmailAddress($replyToSanitized)) {
$mail->addCustomHeader('Reply-To', $replyToSanitized);
}
}
if (isset($config['content-type']) && strlen($config['content-type']) > 0) {
$mail->ContentType = $config['content-type'];
}
if (isset($config['confirm-read']) && $config['confirm-read']) {
$mail->ConfirmReadingTo = $config['from-address'];
}
$mail->Send();
return true;
// ErrorLog::logInfo('Message sent successfully: ' . $config['from-address'] . ' - ' . $subject . ' - ' . print_r($arrto, true) . ' via host ' . $mail->Host);
} catch (phpmailerException $mailError) {
maill_log_send_failure($subject, $arrto, $smtp_host, $smtp_port, $smtp_user, $mail, $mailError, $config);
} catch (Exception $mailError) {
maill_log_send_failure($subject, $arrto, $smtp_host, $smtp_port, $smtp_user, $mail, $mailError, $config);
} finally {
foreach ($stagedTempFiles as $tempFilePath) {
if (is_string($tempFilePath) && file_exists($tempFilePath)) {
@unlink($tempFilePath);
}
}
if (strlen($filePath) > 0 && file_exists($filePath)) {
unlink($filePath);
}
}
return false;
}
}