Your IP : 216.73.217.117


Current Path : /var/www/cesa.co.za/php/includes/
Upload File :
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;
    }
}