Your IP : 216.73.217.79


Current Path : /var/www/cesa.co.za/php/includes/
Upload File :
Current File : /var/www/cesa.co.za/php/includes/Mailer.php

<?php

/*
 * Contact otto@igotafrica.com for license informatrion
 */

/**
 * Help to send mails
 *
 * @author otto@igotafrica.com
 */
class Mailer {

    public static function getMCCredentials() {
        return array(
            'api_key' => 'julia@xe.co.za:f47413d314ebbe6d12a07f057628b68a-us13',
        );
    }

    /**
     * Sanitize a mailbox address before validation.
     * Empty check first; then strip whitespace and collapse repeated dots.
     * For "Name <addr>" forms, display-name spaces are kept; only the address is cleaned.
     *
     * @param mixed $email
     * @return string sanitized address (or "Name <addr>"), empty if nothing usable remains
     */
    public static function sanitizeEmailAddress($email)
    {
        if (!is_string($email)) {
            return '';
        }

        $email = trim($email);
        if ($email === '') {
            return '';
        }

        if (preg_match('/^(.*)<([^>]+)>\s*$/u', $email, $matches)) {
            $name = trim($matches[1]);
            $address = self::sanitizeMailboxPart(trim($matches[2]));
            if ($address === '') {
                return '';
            }
            return $name !== '' ? $name . ' <' . $address . '>' : $address;
        }

        return self::sanitizeMailboxPart($email);
    }

    /**
     * Clean mailbox-only string: whitespace, SA TLD comma typos, consecutive dots.
     *
     * @param string $mailbox
     * @return string
     */
    private static function sanitizeMailboxPart($mailbox)
    {
        if (!is_string($mailbox)) {
            return '';
        }

        $mailbox = trim($mailbox);
        if ($mailbox === '') {
            return '';
        }

        $mailbox = preg_replace('/[\s\x{00A0}]+/u', '', $mailbox);
        if ($mailbox === null || $mailbox === '') {
            return '';
        }

        $mailbox = self::repairSouthAfricanTldCommaTypos($mailbox);

        // pamela@aseda.co..za → pamela@aseda.co.za
        $mailbox = preg_replace('/\.{2,}/', '.', $mailbox);

        // Trim stray dots at ends / next to @
        $mailbox = trim($mailbox, '.');
        $mailbox = str_replace(array('@.', '.@'), array('@', '@'), $mailbox);

        if ($mailbox === '' || strpos($mailbox, '@') === false) {
            return '';
        }

        return $mailbox;
    }

    /**
     * Repair SA TLD typos where a comma was typed instead of a dot before the country code.
     * Example: user@globtek.co,za → user@globtek.co.za
     *
     * @param mixed $value
     * @return string
     */
    public static function repairSouthAfricanTldCommaTypos($value)
    {
        if (!is_string($value) || $value === '') {
            return is_string($value) ? $value : '';
        }

        return preg_replace('/\.(co|org|net|gov|ac|edu),za\b/i', '.$1.za', $value);
    }

    /**
     * Whether the value is a syntactically valid mailbox address.
     * Sanitize first, then reject empty, then validate.
     *
     * @param mixed $email
     * @return bool
     */
    public static function isValidEmailAddress($email)
    {
        if (!is_string($email)) {
            return false;
        }

        // Empty check first (before heavier sanitize work on named forms)
        if (trim($email) === '') {
            return false;
        }

        $mailbox = self::sanitizeEmailAddress($email);
        if (strpos($mailbox, '<') !== false) {
            if (preg_match('/<([^>]+)>/', $mailbox, $matches)) {
                $mailbox = self::sanitizeMailboxPart($matches[1]);
            } else {
                return false;
            }
        }

        if ($mailbox === '' || strpos($mailbox, '@') === false) {
            return false;
        }

        return filter_var($mailbox, FILTER_VALIDATE_EMAIL) !== false;
    }

    /**
     * Split a recipient string or list into unique non-empty raw parts after TLD typo repair.
     * Empty / whitespace-only segments (e.g. from "a@b.com,,c@d.com") are dropped.
     *
     * @param mixed $to
     * @return array
     */
    public static function parseRecipientList($to)
    {
        if (is_array($to)) {
            $merged = array();
            foreach ($to as $item) {
                $merged = array_merge($merged, self::parseRecipientList($item));
            }
            return array_values(array_unique($merged));
        }

        if (!is_string($to) && !is_numeric($to)) {
            return array();
        }

        $to = self::repairSouthAfricanTldCommaTypos((string) $to);
        $parts = preg_split('/\s*[,;]\s*/', $to, -1, PREG_SPLIT_NO_EMPTY);
        if (!is_array($parts)) {
            return array();
        }

        $result = array();
        foreach ($parts as $part) {
            $part = trim($part);
            // Also treat NBSP-only / whitespace-only as empty
            $stripped = preg_replace('/[\s\x{00A0}]+/u', '', $part);
            if ($part === '' || $stripped === null || $stripped === '') {
                continue;
            }
            $result[] = $part;
        }

        return array_values(array_unique($result));
    }

    /**
     * Sanitize and validate a recipient; returns mailbox only, or empty if invalid.
     * Optional by-ref $displayName is filled when input is "Name <addr>".
     *
     * @param mixed $email
     * @param string|null $displayName
     * @return string
     */
    public static function normalizeEmailAddress($email, &$displayName = null)
    {
        $displayName = '';

        if (!is_string($email) || trim($email) === '') {
            return '';
        }

        $sanitized = self::sanitizeEmailAddress($email);
        if ($sanitized === '') {
            return '';
        }

        $mailbox = $sanitized;
        if (strpos($sanitized, '<') !== false) {
            $rcptArr = explode('<', $sanitized, 2);
            $displayName = trim($rcptArr[0]);
            $mailbox = self::sanitizeEmailAddress(str_replace('>', '', $rcptArr[1]));
        }

        // Sanitize already applied; empty check then validate
        if ($mailbox === '' || !self::isValidEmailAddress($mailbox)) {
            $displayName = '';
            return '';
        }

        return $mailbox;
    }

    public static function extractHeaders($rawHeaders) {

        $lines = explode("\n", $rawHeaders);
        $return = array();

        foreach ($lines as $line) {
            $lineData = explode(': ', $line);

            if ($lineData[0] == 'From') {

                $nameFrom = explode('<', $lineData[1]);

                if (count($nameFrom) > 0) {
                    $return['from-name'] = trim($nameFrom[0]);
                    $return['from-address'] = str_replace('>', '', $nameFrom[1]);
                } else {
                    $return['from-address'] = $lineData[1];
                }
            } elseif ($lineData[0] == 'Reply-To') {
                $return['reply-to-address'] = $lineData[1];
            } elseif ($lineData[0] == 'Content-Type') {
                $return['content-type'] = trim(substr($rawHeaders, strpos($rawHeaders, 'Content-Type') + 13));
            } elseif ($lineData[0] == 'Content-Type') {
                $return['confirm-read'] = true;
            }
        }

        return $return;
    }

    /**
     * Escape text for iCalendar (RFC 5545): backslash, semicolon, comma, newline.
     * Outlook and other clients require this for SUMMARY, DESCRIPTION, LOCATION.
     */
    public static function escapeIcalText($value) {
        if ($value === null || $value === '') {
            return '';
        }
        return str_replace(array('\\', ';', ',', "\r\n", "\n", "\r"), array('\\\\', '\\;', '\\,', '\\n', '\\n', '\\n'), $value);
    }

    /**
     * Build RFC 5545 / Outlook-compatible .ics calendar invite.
     * Event times are interpreted as South Africa (Africa/Johannesburg, UTC+2)
     * and converted to UTC in the .ics so Outlook and other clients show the correct local time.
     */
    public static function getCalendarInviteUid($marketingEvent, $eventAttendee)
    {
        $eventId = isset($marketingEvent['EventID']) ? intval($marketingEvent['EventID']) : 0;
        $attendeeId = isset($eventAttendee['AttendeeID']) ? intval($eventAttendee['AttendeeID']) : 0;

        if ($eventId > 0 && $attendeeId > 0) {
            return 'marketing-event-' . $eventId . '-attendee-' . $attendeeId . '@cesa.co.za';
        }

        return md5(uniqid(mt_rand(), true)) . '@cesa.co.za';
    }

    public static function getICalText($marketingEvent, $eventAttendee, $options = array()) {

        $isUpdate = !empty($options['isUpdate']);
        $changeMessage = isset($options['changeMessage']) ? trim((string) $options['changeMessage']) : '';
        $sequence = isset($options['sequence']) ? intval($options['sequence']) : ($isUpdate ? time() : 0);
        $uid = isset($options['uid']) && strlen($options['uid']) > 0
            ? $options['uid']
            : self::getCalendarInviteUid($marketingEvent, $eventAttendee);

        $tzSA = new \DateTimeZone('Africa/Johannesburg');
        $tzUTC = new \DateTimeZone('UTC');
        $startDateTime = new \DateTime($marketingEvent['StartDate'] . ' ' . $marketingEvent['StartTime'], $tzSA);
        $endDateTime = new \DateTime($marketingEvent['EndDate'] . ' ' . $marketingEvent['EndTime'], $tzSA);
        $startDateTime->setTimezone($tzUTC);
        $endDateTime->setTimezone($tzUTC);

        $venue = isset($marketingEvent['Venue']) ? $marketingEvent['Venue'] : '';
        if (isset($marketingEvent['City']) && strlen($marketingEvent['City']) > 0) {
            $venue .= ($venue !== '' ? ', ' : '') . $marketingEvent['City'];
        }
        if (isset($marketingEvent['Province']) && strlen($marketingEvent['Province']) > 0) {
            $venue .= ($venue !== '' ? ', ' : '') . $marketingEvent['Province'];
        }

        $summary = isset($marketingEvent['EventName']) ? $marketingEvent['EventName'] : '';
        $description = isset($marketingEvent['EventDescription']) ? $marketingEvent['EventDescription'] : '';
        if ($isUpdate) {
            $description = 'This event has been updated.' . ($description !== '' ? "\n\n" . $description : '');
        }
        if ($changeMessage !== '') {
            $description .= ($description !== '' ? "\n\n" : '') . $changeMessage;
        }

        $attendeeName = trim(($eventAttendee['ContactFirstName'] ?? '') . ' ' . ($eventAttendee['ContactLastName'] ?? ''));

        $text = "BEGIN:VCALENDAR\r\n"
                . "VERSION:2.0\r\n"
                . "PRODID:-//CESA//events/NONSGML v1.0//EN\r\n"
                . "CALSCALE:GREGORIAN\r\n"
                . "METHOD:REQUEST\r\n"
                . "BEGIN:VTIMEZONE\r\n"
                . "TZID:Africa/Johannesburg\r\n"
                . "BEGIN:STANDARD\r\n"
                . "DTSTART:19700101T000000\r\n"
                . "TZOFFSETFROM:+0200\r\n"
                . "TZOFFSETTO:+0200\r\n"
                . "TZNAME:SAST\r\n"
                . "END:STANDARD\r\n"
                . "END:VTIMEZONE\r\n"
                . "BEGIN:VTIMEZONE\r\n"
                . "TZID:UTC\r\n"
                . "BEGIN:STANDARD\r\n"
                . "DTSTART:19700101T000000Z\r\n"
                . "TZOFFSETFROM:+0000\r\n"
                . "TZOFFSETTO:+0000\r\n"
                . "TZNAME:UTC\r\n"
                . "END:STANDARD\r\n"
                . "END:VTIMEZONE\r\n"
                . "BEGIN:VEVENT\r\n"
                . "UID:" . $uid . "\r\n"
                . "SEQUENCE:" . $sequence . "\r\n"
                . "DTSTAMP:" . gmdate('Ymd\THis\Z') . "\r\n"
                . "DTSTART:" . $startDateTime->format('Ymd\THis\Z') . "\r\n"
                . "DTEND:" . $endDateTime->format('Ymd\THis\Z') . "\r\n"
                . "SUMMARY:" . self::escapeIcalText($summary) . "\r\n"
                . "ORGANIZER;CN=CESA:mailto:events@cesa.co.za\r\n"
                . "LOCATION:" . self::escapeIcalText($venue) . "\r\n"
                . "DESCRIPTION:" . self::escapeIcalText($description) . "\r\n";

        if (isset($marketingEvent['BannerImgURL']) && strlen($marketingEvent['BannerImgURL']) > 0) {

            $eventBanner = @file_get_contents($marketingEvent['BannerImgURL']);

            if ($eventBanner !== false && strlen($eventBanner) > 0) {

                $fileName = basename($marketingEvent['BannerImgURL']);

                $text .= "ATTACH;ENCODING=BASE64;VALUE=BINARY;X-FILENAME=" . $fileName . ":" . base64_encode($eventBanner) . "\r\n";
            }
        }

        if (false) {
            // Add URL for event later
            $text .= "URL;VALUE=URI:https://www.redacted.com" . "\r\n";
        }

        $attendeeCn = $attendeeName !== '' ? ';CN="' . str_replace(array('\\', '"'), array('\\\\', '\\"'), $attendeeName) . '"' : '';
        $text .= "ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP=TRUE" . $attendeeCn . ";X-NUM-GUESTS=0:MAILTO:" . $eventAttendee['EmailAddress'] . "\r\n"
                . "END:VEVENT\r\n"
                . "END:VCALENDAR\r\n";
        return $text;
    }
}