Linux cesa-www-main 6.1.0-49-cloud-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.174-1 (2026-05-26) x86_64
Apache/2.4.68 (Debian)
Server IP : 10.218.0.2 & Your IP : 216.73.216.28
Domains :
Cant Read [ /etc/named.conf ]
User : www-data
Terminal
Auto Root
Create File
Create Folder
Localroot Suggester
Backdoor Destroyer
Readme
/
var /
www /
cesa.co.za /
crons /
Delete
Unzip
Name
Size
Permission
Date
Action
Mandrill
[ DIR ]
drwxr-sr-x
2016-02-10 08:42
temp
[ DIR ]
drwxr-sr-x
2022-06-16 13:37
.htaccess
35
B
-rw-r--r--
2023-03-13 09:52
CESA_Ci Applications2b.pdf
800.6
KB
-rw-r--r--
2008-08-14 11:23
CESA_vector logo_high res.jpg
334.84
KB
-rw-r--r--
2008-08-14 11:10
Fulton_Press_small.pdf
84.39
KB
-rw-r--r--
2008-08-11 10:41
LAUNCH OF CESA.pdf
69.23
KB
-rw-r--r--
2008-08-14 11:24
Mandrill.php
6.44
KB
-rw-r--r--
2016-02-10 08:42
Overview_template_QMS_for_SMME-v2 _2.pdf
43.08
KB
-rw-r--r--
2008-09-26 12:04
ToSendProcess.php
46.4
KB
-rw-r--r--
2026-04-01 10:55
XPertMailer.php
38.69
KB
-rw-r--r--
2008-08-06 07:29
ad-reminders.php
2.7
KB
-rw-r--r--
2024-05-16 09:16
awards-from-forminator-debug.php
7.13
KB
-rw-r--r--
2025-06-23 16:02
awards-from-forminator-direct.php
9.69
KB
-rw-r--r--
2025-06-24 06:23
awards-from-forminator-fixed.php
7.42
KB
-rw-r--r--
2025-06-23 16:09
awards-from-forminator-step-by-step.php
6.93
KB
-rw-r--r--
2025-06-23 16:12
awards-from-forminator.php
18.54
KB
-rw-r--r--
2026-06-29 10:02
batch-job.php
948
B
-rw-r--r--
2026-08-24 10:20
bulk-mail-check.php
443
B
-rw-r--r--
2025-08-22 11:48
checkmail.php
6.35
KB
-rw-r--r--
2008-08-06 09:18
class.email-reader.php
15.58
KB
-rw-r--r--
2008-08-06 09:18
cron-5-minutes-root.sh
527
B
-rwxr-xr-x
2026-08-21 08:23
cron-daily-v3.sh
192
B
-rwxr-xr-x
2025-09-11 17:37
cron-daily.sh
500
B
-rwxr-xr-x
2025-12-29 09:32
cron-hourly.sh
413
B
-rwxr-xr-x
2025-09-01 13:33
cron-monthly.sh
83
B
-rwxr-xr-x
2025-06-27 15:38
cron.sh
369
B
-rwxr-xr-x
2026-04-16 10:02
export-attendees-google.php
3.07
KB
-rw-r--r--
2026-08-24 10:20
export-member-data-google.php
3.69
KB
-rw-r--r--
2026-08-24 10:20
export.php
1.96
KB
-rw-r--r--
2010-04-13 14:54
exportgooglecontacts.php
1.18
KB
-rw-r--r--
2022-06-16 14:41
import.php.bak
4.84
KB
-rw-r--r--
2010-05-06 07:59
importdup.php
3.34
KB
-rw-r--r--
2010-05-19 14:20
importschool.php
5.09
KB
-rw-r--r--
2010-05-19 14:05
php8.1_cli_errors.log
43.15
KB
-rw-r--r--
2025-12-31 10:58
phpinfo.php
20
B
-rw-r--r--
2024-04-16 11:40
receivemail.class.php
4.65
KB
-rw-r--r--
2008-08-06 07:51
sce-send-certificates.php
750
B
-rw-r--r--
2026-08-24 10:20
sendlater.php
12.86
KB
-rw-r--r--
2026-01-16 09:53
sendsms.php
4.3
KB
-rw-r--r--
2026-01-16 09:43
simple-wp-test.php
1.74
KB
-rw-r--r--
2025-06-23 16:05
sync-anual-declarations.php
3.22
KB
-rw-r--r--
2026-08-24 10:20
system-checks.php
798
B
-rw-r--r--
2026-08-24 10:20
test-send.php
786
B
-rw-r--r--
2024-04-11 07:55
test-wp-load.php
2.29
KB
-rw-r--r--
2025-06-23 16:02
training-material-expiry-check.php
4.12
KB
-rw-r--r--
2026-06-12 11:48
training-provider-expiry-check.php
4.51
KB
-rw-r--r--
2026-06-12 11:48
unemployed-reminders.php
2.43
KB
-rw-r--r--
2024-05-16 09:06
update-qms-cert-status.php
3.54
KB
-rw-r--r--
2026-08-24 10:20
wp-diagnostic.php
4.39
KB
-rw-r--r--
2025-06-23 16:08
wp-load-bypass.php
3.03
KB
-rw-r--r--
2025-06-23 16:08
wp-load-minimal.php
3.01
KB
-rw-r--r--
2025-06-23 16:08
Save
Rename
<?php //if ($_GET["override"] != 1) die(); ini_set('max_execution_time', '0'); // 0 = no limit //ini_set('display_errors', 1); //error_reporting(E_ALL); ob_start(); $maxmsgs = 10; $loopMax = 3; if (!isset($_SERVER['DOCUMENT_ROOT']) || strlen($_SERVER['DOCUMENT_ROOT']) <= 0) { $_SERVER['DOCUMENT_ROOT'] = str_replace('/crons', '', getcwd()); // echo getcwd(); } // Check if process is running using PID-based lock file $lockFile = $_SERVER['DOCUMENT_ROOT'] . '/crons/ToSendProcess.lock'; if (file_exists($lockFile)) { $lockContent = file_get_contents($lockFile); $lockData = explode('|', $lockContent); $storedPid = isset($lockData[0]) ? intval($lockData[0]) : 0; $lockTime = isset($lockData[1]) ? intval($lockData[1]) : 0; // Check if the stored PID is actually running $processRunning = false; if ($storedPid > 0) { // On Linux/Unix, check if process exists if (function_exists('posix_kill')) { // Use posix_kill with signal 0 to check if process exists (doesn't actually kill) $processRunning = @posix_kill($storedPid, 0); } else { // Fallback: check using ps command $psOutput = @shell_exec("ps -p $storedPid -o pid= 2>/dev/null"); $processRunning = !empty(trim($psOutput)); } } // Also check if lock file is very old (more than 12 hours) as a safety measure $currentTime = time(); $lockAge = $currentTime - $lockTime; $maxLockAge = 60 * 60 * 12; // 12 hours if ($processRunning) { // Process is actually running echo "Process already running (PID: $storedPid). Exiting.\n"; echo "Lock file created at: " . date('Y-m-d H:i:s', $lockTime) . "\n"; exit; } elseif ($lockAge < $maxLockAge && $storedPid > 0) { // Lock file is recent but PID doesn't exist - might be a race condition // Wait a moment and check again sleep(2); if (function_exists('posix_kill')) { $processRunning = @posix_kill($storedPid, 0); } else { $psOutput = @shell_exec("ps -p $storedPid -o pid= 2>/dev/null"); $processRunning = !empty(trim($psOutput)); } if ($processRunning) { echo "Process already running (PID: $storedPid). Exiting.\n"; exit; } } // Lock file exists but process is not running, or lock is very old - remove it echo "Removing stale lock file (PID: $storedPid, Age: " . round($lockAge / 3600, 2) . " hours).\n"; unlink($lockFile); } // Create the lock file with current PID and timestamp $currentPid = getmypid(); $lockContent = $currentPid . '|' . time(); file_put_contents($lockFile, $lockContent); try { //echo $_SERVER['DOCUMENT_ROOT']; //exit; include_once($_SERVER['DOCUMENT_ROOT'] . "/cfg/config.php"); include_once($_SERVER['DOCUMENT_ROOT'] . "/php/includes/ErrorLog.php"); include_once($_SERVER['DOCUMENT_ROOT'] . "/php/includes/maill.php"); include_once($_SERVER['DOCUMENT_ROOT'] . "/php/includes/BaseTableClass.php"); include_once($_SERVER['DOCUMENT_ROOT'] . "/php/includes/mail/MailMessages.php"); //include_once($_SERVER['DOCUMENT_ROOT'] . "/php/test_data/raw_email.php"); echo "ToSendProcess: Script started at " . date('Y-m-d H:i:s') . "\n"; ErrorLog::logInfo('ToSendProcess: Script started at ' . date('Y-m-d H:i:s')); $db = mysqli_connect(EW_CONN_HOST, MAILDB_USER, MAILDB_PASS, MAILDB_DB); //$testMailsSQL = "SELECT * FROM Messages ORDER BY RAND() LIMIT 0, 10"; //$testMailsRec = mysqli_query($db, $testMailsSQL); //while ($testMail = mysqli_fetch_array($testMailsRec)) { // maill('otto@igotafrica.com', $testMail['Subject'], $testMail['Message'], null, array('headers_raw' => $testMail['Headers'])); //} $bCanSend = false; $bSomethingSent = false; $sql = "SELECT StartSend, EndSend FROM LastSend"; $rsRec = mysqli_query($db, $sql); if ($rsR = mysqli_fetch_assoc($rsRec)) { // Has 15 seconds passed since the last send? if ((time() - strtotime($rsR["StartSend"])) >= 15) { $bCanSend = true; // Or, was the last start a loooong time ago? } elseif ((time() - strtotime($rsR["StartSend"])) >= 10) { $bCanSend = true; } } ErrorLog::logInfo('ToSendProcess: bCanSend check result: ' . ($bCanSend ? 'true' : 'false')); if ($bCanSend) { // Get the messages $sql = "SELECT * FROM Messages WHERE FromSchool=0 AND SendComplete=0 AND EXISTS (SELECT * FROM ToSend WHERE ToSend.MessageID=Messages.MessageID AND ToSend.Sent=0) ORDER BY Messages.Prioritise DESC, Messages.MessageID"; $rsMsg = mysqli_query($db, $sql); if ($rsM = mysqli_fetch_assoc($rsMsg)) { echo "ToSendProcess: Found message ID " . $rsM["MessageID"] . " with Subject: " . $rsM["Subject"] . "\n"; echo "ToSendProcess: MessageFrom: " . $rsM["MessageFrom"] . "\n"; echo "ToSendProcess: Options: " . substr($rsM["Options"], 0, 200) . "\n"; ErrorLog::logInfo('ToSendProcess: Found message ID ' . $rsM["MessageID"] . ' with MessageFrom: ' . $rsM["MessageFrom"]); } else { echo "ToSendProcess: No messages found to send.\n"; ErrorLog::logInfo('ToSendProcess: No messages found - SQL: ' . $sql); $bCanSend = false; // maill('cesa-bulk-mail@igotafrica.com', 'CESA Mailing List Error', 'Select from messages failed: ' . $sql); } } else { echo "ToSendProcess: Cannot send yet (timing restrictions).\n"; ErrorLog::logInfo('ToSendProcess: Cannot send yet - timing restrictions not met'); unlink($lockFile); } if ($bCanSend) { for ($loopCnt = 1; $loopCnt <= $loopMax; $loopCnt++) { //Send 20 messages 10 times // Make sure another instance of this script doesn't start sending!! $sql = "UPDATE LastSend SET StartSend='" . date("Y-m-d H:i:s") . "', EndSend='2020-01-01'"; mysqli_query($db, $sql); $iMsgsSent = 0; $sql = "SELECT ToSend.ToSendID, ToSend.SendTo, ToSend.Message FROM ToSend WHERE ToSend.MessageID=" . $rsM["MessageID"] . " and ToSend.Sent=0 and ToSend.SendTo LIKE '%@cesa.co.za' ORDER BY ToSend.SendTo LIMIT 0," . $maxmsgs; // echo $sql . "\r\n"; $rsRec = mysqli_query($db, $sql); if (mysqli_num_rows($rsRec) <= 0) { $sql = "SELECT ToSend.ToSendID, ToSend.SendTo, ToSend.Message FROM ToSend WHERE ToSend.MessageID=" . $rsM["MessageID"] . " and ToSend.Sent=0 and NOT (ToSend.SendTo LIKE '%@cesa.co.za') ORDER BY ToSend.SendTo LIMIT 0," . $maxmsgs; // echo $sql . "\r\n"; $rsRec = mysqli_query($db, $sql); } if (mysqli_num_rows($rsRec) >= 4000) { maill('cesa-bulk-mail@igotafrica.com', 'CESA Mailing List Backed Up', 'Messages waiting to be sent: ' . mysqli_num_rows($rsRec)); } while ($rsR = mysqli_fetch_assoc($rsRec)) { $sMessage = stripslashes($rsM["Message"]); if (!empty($rsR["Message"])) $sMessage = stripslashes($rsR["Message"]); if (strlen((string) $sMessage) <= 0 || strlen((string) $rsM["Subject"]) <= 0) { $sql = "UPDATE ToSend SET Sent=2 WHERE ToSendID=" . intval($rsR["ToSendID"]); mysqli_query($db, $sql); ErrorLog::logInfo('ToSendProcess: Message ID ' . $rsM["MessageID"] . ' has no message or subject. Skipping. ' . print_r($rsR, true)); continue; } // Extract attachments and embedded images from raw email if message looks like raw email // If ToSend.Message is personalized HTML, try Messages.Message for raw email $extractedAttachments = []; $embeddedImages = []; $finalMessage = $sMessage; $rawEmailToParse = $sMessage; // If ToSend.Message doesn't look like raw email but Messages.Message might be, use that $hasHeaders = (strpos($sMessage, "\r\n\r\n") !== false || strpos($sMessage, "\n\n") !== false); $hasContentType = (strpos($sMessage, 'Content-Type:') !== false || strpos($sMessage, 'boundary=') !== false); if (!$hasHeaders || !$hasContentType) { // ToSend.Message is likely personalized HTML, try Messages.Message $messagesMessage = stripslashes($rsM["Message"]); if (!empty($messagesMessage) && $messagesMessage !== $sMessage) { $msgsHasHeaders = (strpos($messagesMessage, "\r\n\r\n") !== false || strpos($messagesMessage, "\n\n") !== false); $msgsHasContentType = (strpos($messagesMessage, 'Content-Type:') !== false || strpos($messagesMessage, 'boundary=') !== false); if ($msgsHasHeaders && $msgsHasContentType) { $rawEmailToParse = $messagesMessage; ErrorLog::logInfo('ToSendProcess: Using Messages.Message for raw email parsing (ToSend.Message appears to be personalized)'); } } } // Check if message looks like a raw email (contains headers and multipart structure) $checkHeaders = (strpos($rawEmailToParse, "\r\n\r\n") !== false || strpos($rawEmailToParse, "\n\n") !== false); $checkContentType = (strpos($rawEmailToParse, 'Content-Type:') !== false || strpos($rawEmailToParse, 'boundary=') !== false); $mailparseAvailable = extension_loaded('mailparse'); ErrorLog::logInfo('ToSendProcess: Checking raw email format - mailparse available: ' . ($mailparseAvailable ? 'yes' : 'no') . ', hasHeaders: ' . ($checkHeaders ? 'yes' : 'no') . ', hasContentType: ' . ($checkContentType ? 'yes' : 'no') . ', message to parse length: ' . strlen($rawEmailToParse)); if ($mailparseAvailable && $checkHeaders && $checkContentType) { ErrorLog::logInfo('ToSendProcess: Message looks like raw email, attempting to parse...'); try { $parsed = MailMessages::parseRawEmail($rawEmailToParse); ErrorLog::logInfo('ToSendProcess: Raw email parsed successfully - HTML length: ' . (isset($parsed['html']) ? strlen($parsed['html']) : 0) . ', Text length: ' . (isset($parsed['text']) ? strlen($parsed['text']) : 0) . ', Attachments: ' . (isset($parsed['attachments']) ? count($parsed['attachments']) : 0)); // Use HTML content if available, otherwise text if (!empty($parsed['html'])) { $finalMessage = $parsed['html']; } elseif (!empty($parsed['text'])) { $finalMessage = $parsed['text']; } if (!empty($finalMessage)) { echo "ToSendProcess: Extracted " . (!empty($parsed['html']) ? 'HTML' : 'text') . " content from raw email, length: " . strlen($finalMessage) . "\n"; } // Extract attachments and inline images if (!empty($parsed['attachments']) && is_array($parsed['attachments'])) { $tempDir = sys_get_temp_dir(); foreach ($parsed['attachments'] as $idx => $attachment) { if (isset($attachment['content']) && !empty($attachment['content'])) { $filename = isset($attachment['filename']) ? $attachment['filename'] : 'attachment_' . $idx; // Clean filename but preserve extension $pathInfo = pathinfo($filename); $baseName = isset($pathInfo['filename']) ? $pathInfo['filename'] : $filename; $extension = isset($pathInfo['extension']) ? '.' . $pathInfo['extension'] : ''; // Clean base name (remove invalid characters) $baseName = preg_replace('/[^a-zA-Z0-9._-]/', '_', $baseName); if (empty($baseName)) { $baseName = 'attachment_' . $idx; } // Reconstruct filename with extension preserved $filename = $baseName . $extension; // Create temp path with timestamp/random but preserve extension $tempPath = $tempDir . '/' . $baseName . '_' . time() . '_' . mt_rand(1000, 9999) . $extension; if (file_put_contents($tempPath, $attachment['content'])) { // Check if this is an embedded image (has Content-ID and is an image type) $contentType = $attachment['content_type'] ?? ''; $contentId = isset($attachment['content_id']) ? trim($attachment['content_id'], '<>') : ''; // Debug logging ErrorLog::logInfo('ToSendProcess: Processing attachment - filename: ' . $filename . ', content_type: ' . $contentType . ', content_id: "' . $contentId . '"'); if (!empty($contentId) && strpos($contentType, 'image/') !== false) { // This is an embedded image, add it to embedded images $cid = trim($contentId, '<>'); $embeddedImages[] = [ 'cid' => $cid, 'path' => $tempPath, 'content_type' => $contentType ]; echo "ToSendProcess: Found embedded image with CID: " . $cid . " (Content-Type: " . $contentType . ")\n"; ErrorLog::logInfo('ToSendProcess: Added embedded image - CID: "' . $cid . '", Path: ' . $tempPath . ', Content-Type: ' . $contentType); } else { // This is a regular attachment // Store both path and original filename to preserve extension $extractedAttachments[] = [ 'path' => $tempPath, 'name' => $filename // Original filename with extension preserved ]; echo "ToSendProcess: Extracted attachment: " . $filename . " (" . strlen($attachment['content']) . " bytes)\n"; if (empty($contentId)) { ErrorLog::logInfo('ToSendProcess: Attachment has no Content-ID - treating as regular attachment'); } elseif (strpos($contentType, 'image/') === false) { ErrorLog::logInfo('ToSendProcess: Attachment has Content-ID but is not an image type - treating as regular attachment'); } } } } } ErrorLog::logInfo('ToSendProcess: Extracted ' . count($extractedAttachments) . ' attachments and ' . count($embeddedImages) . ' embedded images from raw email'); } } catch (Exception $parseEx) { ErrorLog::logInfo('ToSendProcess: Failed to parse raw email: ' . $parseEx->getMessage() . ' - Trace: ' . $parseEx->getTraceAsString()); // Continue with original message if parsing fails $finalMessage = $sMessage; } } else { ErrorLog::logInfo('ToSendProcess: Message does not look like raw email - skipping attachment/image extraction. Message preview: ' . substr($sMessage, 0, 200)); } // Update message to use extracted content $sMessage = $finalMessage; // echo $sMessage . ' is the message' . "\r\n"; // Decode quoted-printable if message contains encoded sequences // But be careful not to corrupt HTML content $isHtmlMessage = (stripos($sMessage, '<html') !== false || stripos($sMessage, '<body') !== false || stripos($sMessage, '<div') !== false || stripos($sMessage, '<p') !== false); // Only decode when there are clear quoted-printable soft-break markers. // Decoding solely because "=3D" exists can corrupt normal URLs like "?id=511" // by interpreting "=51" as quoted-printable hex ("Q"). $hasSoftBreaks = (bool) preg_match('/=\s*\r?\n/', $sMessage); if ($hasSoftBreaks) { $decoded = quoted_printable_decode($sMessage); if (!empty($decoded)) { $sMessage = $decoded; } ErrorLog::logInfo('ToSendProcess: Quoted-printable decoded (HTML: ' . ($isHtmlMessage ? 'true' : 'false') . '), message length: ' . strlen($sMessage)); } // Verify HTML structure after decoding if ($isHtmlMessage) { echo "ToSendProcess: Message is HTML (contains HTML tags)\n"; if (stripos($sMessage, '<html') === false && stripos($sMessage, '<body') === false && stripos($sMessage, '<div') === false && stripos($sMessage, '<p') === false) { echo "ToSendProcess: WARNING - HTML tags lost during processing!\n"; ErrorLog::logInfo('ToSendProcess: WARNING - HTML structure may have been corrupted'); } } // Extract from-name and from-address from MessageFrom field if available $fromName = ''; $fromAddress = ''; if (!empty($rsM['MessageFrom'])) { $messageFrom = trim($rsM['MessageFrom']); echo "ToSendProcess: Processing MessageFrom: " . $messageFrom . "\n"; if (strpos($messageFrom, '<') !== false && strpos($messageFrom, '>') !== false) { // Format: "Name <email@domain.com>" $fromParts = explode('<', $messageFrom); $fromName = trim($fromParts[0]); // Strip quotes (single and double) from the name $fromName = trim($fromName, '\'"'); if (count($fromParts) > 1) { $fromAddress = trim(str_replace('>', '', $fromParts[1])); } echo "ToSendProcess: Extracted from MessageFrom - Name: \"" . $fromName . "\", Address: \"" . $fromAddress . "\"\n"; } else { // Format: "email@domain.com" (no name) $fromAddress = trim($messageFrom); echo "ToSendProcess: MessageFrom has no name, Address: \"" . $fromAddress . "\"\n"; } // Debug logging ErrorLog::logInfo('ToSendProcess: Extracted from MessageFrom - Name: "' . $fromName . '", Address: "' . $fromAddress . '"'); } else { echo "ToSendProcess: WARNING - MessageFrom is empty!\n"; } // Prioritize Options over Headers - Options contains the correct content-type and isHTML flag if ($rsM['Options'] != '') { echo "ToSendProcess: Processing Options field...\n"; // Try to decode JSON - handle both escaped and non-escaped JSON $optionsJson = $rsM['Options']; $options = json_decode($optionsJson, true); // If decoding failed, try with stripslashes if ($options === null && json_last_error() !== JSON_ERROR_NONE) { echo "ToSendProcess: Initial JSON decode failed, trying with stripslashes...\n"; $options = json_decode(stripslashes($optionsJson), true); ErrorLog::logInfo('ToSendProcess: JSON decode required stripslashes, original: ' . substr($optionsJson, 0, 200)); } if (is_array($options)) { echo "ToSendProcess: Options JSON decoded successfully\n"; echo "ToSendProcess: Options - from-name: \"" . (isset($options['from-name']) ? $options['from-name'] : 'not set') . "\", isHTML: " . (isset($options['isHTML']) ? ($options['isHTML'] ? 'true' : 'false') : 'not set') . "\n"; // Preserve the 'source' field to identify bulk mails if (isset($options['source'])) { echo "ToSendProcess: Options - source: \"" . $options['source'] . "\" (bulk mail detected)\n"; } } else { echo "ToSendProcess: ERROR - Options JSON decode FAILED! Error: " . json_last_error_msg() . "\n"; } // Debug: Log the Options field value and decoded result ErrorLog::logInfo('ToSendProcess: Options JSON: ' . substr($optionsJson, 0, 200)); ErrorLog::logInfo('ToSendProcess: Options decoded: ' . print_r($options, true)); // Clean the options to fix escaped slashes and other issues if (is_array($options)) { // Fix escaped content-type if (isset($options['content-type'])) { $options['content-type'] = str_replace('\\/', '/', $options['content-type']); } ErrorLog::logInfo('ToSendProcess: Content type is ' . (isset($options['content-type']) ? $options['content-type'] : 'not set')); // Ensure isHTML is properly set - prioritize explicit value from Options if (isset($options['isHTML'])) { // Use the explicit isHTML value from Options $options['isHTML'] = (bool)$options['isHTML']; echo "ToSendProcess: Set isHTML from Options: " . ($options['isHTML'] ? 'true' : 'false') . "\n"; } elseif (isset($options['content-type']) && strpos($options['content-type'], 'text/html') !== false) { $options['isHTML'] = true; echo "ToSendProcess: Set isHTML=true from content-type: " . $options['content-type'] . "\n"; } elseif (isset($options['content-type']) && strpos($options['content-type'], 'text/plain') !== false) { $options['isHTML'] = false; echo "ToSendProcess: Set isHTML=false from content-type: " . $options['content-type'] . "\n"; } else { // Fallback: detect HTML from content $options['isHTML'] = (stripos($sMessage, '<html') !== false || stripos($sMessage, '<body') !== false || stripos($sMessage, '<div') !== false || stripos($sMessage, '<p') !== false); echo "ToSendProcess: Detected isHTML from message content: " . ($options['isHTML'] ? 'true' : 'false') . "\n"; } // Set content-type based on isHTML if not explicitly set if (!isset($options['content-type']) || empty($options['content-type'])) { $options['content-type'] = $options['isHTML'] ? 'text/html' : 'text/plain'; echo "ToSendProcess: Set content-type based on isHTML: " . $options['content-type'] . "\n"; } // Priority: MessageFrom > Options // Set from-name - Always use sender's name from the email being processed // For bulk mails, the sender's name should be in Options (set during email processing) if (!empty($fromName)) { // Extract from MessageFrom if available $options['from-name'] = trim($fromName, '\'"'); echo "ToSendProcess: SET from-name from MessageFrom: \"" . $options['from-name'] . "\"\n"; ErrorLog::logInfo('ToSendProcess: Set from-name from MessageFrom: "' . $options['from-name'] . '"'); } elseif (isset($options['from-name']) && !empty(trim($options['from-name']))) { // Use from-name from Options (this should be the sender's name from the processed email) $options['from-name'] = trim($options['from-name'], '\'"'); echo "ToSendProcess: Using from-name from Options (sender's name): \"" . $options['from-name'] . "\"\n"; ErrorLog::logInfo('ToSendProcess: Using from-name from Options (sender\'s name): "' . $options['from-name'] . '"'); } else { // Try to extract from MessageFrom as last resort if (!empty($rsM['MessageFrom'])) { $messageFrom = trim($rsM['MessageFrom']); if (strpos($messageFrom, '<') !== false && strpos($messageFrom, '>') !== false) { $fromParts = explode('<', $messageFrom); $extractedName = trim($fromParts[0], '\'"'); if (!empty($extractedName)) { $options['from-name'] = $extractedName; echo "ToSendProcess: Extracted from-name from MessageFrom as fallback: \"" . $options['from-name'] . "\"\n"; ErrorLog::logInfo('ToSendProcess: Extracted from-name from MessageFrom as fallback: "' . $options['from-name'] . '"'); } } } } // Final cleanup: ensure from-name is clean (no quotes) but don't set default // For bulk mails, we want to use the sender's name, not a default if (isset($options['from-name'])) { $options['from-name'] = trim($options['from-name'], '\'"'); } // Set from-address - MessageFrom > Options > extract from reply-to if (!empty($fromAddress)) { $options['from-address'] = trim($fromAddress); // Also set reply-to to the same address $options['reply-to-address'] = $options['from-address']; ErrorLog::logInfo('ToSendProcess: Set from-address from MessageFrom: "' . $options['from-address'] . '"'); } elseif (isset($options['reply-to-address']) && !empty($options['reply-to-address'])) { // Use reply-to as from-address if available if (!isset($options['from-address']) || empty(trim($options['from-address']))) { $options['from-address'] = trim($options['reply-to-address']); ErrorLog::logInfo('ToSendProcess: Using reply-to-address as from-address: "' . $options['from-address'] . '"'); } } elseif (isset($options['from-address'])) { $options['from-address'] = trim($options['from-address']); } // Debug logging - show final options being sent echo "ToSendProcess: FINAL OPTIONS - from-name: \"" . (isset($options['from-name']) ? $options['from-name'] : 'not set') . "\", from-address: \"" . (isset($options['from-address']) ? $options['from-address'] : 'not set') . "\", isHTML: " . ($options['isHTML'] ? 'true' : 'false') . ", content-type: " . (isset($options['content-type']) ? $options['content-type'] : 'not set') . "\n"; echo "ToSendProcess: Calling maill() to send to: " . $rsR["SendTo"] . "\n"; // Add extracted attachments to options if (!empty($extractedAttachments)) { if (!isset($options['attachments']) || !is_array($options['attachments'])) { $options['attachments'] = []; } $options['attachments'] = array_merge($options['attachments'], $extractedAttachments); ErrorLog::logInfo('ToSendProcess: Added ' . count($extractedAttachments) . ' extracted attachments to options'); } // Add embedded images to options if (!empty($embeddedImages)) { $options['embedded_images'] = $embeddedImages; echo "ToSendProcess: Added " . count($embeddedImages) . " embedded images to options\n"; foreach ($embeddedImages as $idx => $img) { echo "ToSendProcess: Embedded image " . ($idx + 1) . " - CID: \"" . $img['cid'] . "\", Path: " . $img['path'] . ", Type: " . $img['content_type'] . "\n"; // Check if CID appears in HTML message $cidInHtml = (stripos($sMessage, 'cid:' . $img['cid']) !== false || stripos($sMessage, '"cid:' . $img['cid'] . '"') !== false); echo "ToSendProcess: CID \"" . $img['cid'] . "\" found in HTML: " . ($cidInHtml ? 'YES' : 'NO') . "\n"; } ErrorLog::logInfo('ToSendProcess: Added ' . count($embeddedImages) . ' embedded images to options'); } else { echo "ToSendProcess: No embedded images to add\n"; } // Final cleanup: ensure from-name is clean (no quotes) // For bulk mails, we want to use the sender's name from the email, not a default if (isset($options['from-name'])) { $options['from-name'] = trim($options['from-name'], '\'"'); } // ErrorLog::logInfo('ToSendProcess: Final options being sent to maill() - from-name: "' . (isset($options['from-name']) ? $options['from-name'] : 'not set') . '", from-address: "' . (isset($options['from-address']) ? $options['from-address'] : 'not set') . '", isHTML: ' . ($options['isHTML'] ? 'true' : 'false') . ', content-type: ' . (isset($options['content-type']) ? $options['content-type'] : 'not set') . ', attachments: ' . (isset($options['attachments']) ? count($options['attachments']) : 0)); // ErrorLog::logInfo('ToSendProcess: Sending email to ' . $rsR["SendTo"] . ' with subject: ' . $rsM["Subject"]); // ErrorLog::logInfo('ToSendProcess: Message preview: ' . substr($sMessage, 0, 200)); maill($rsR["SendTo"], $rsM["Subject"], $sMessage, null, $options); echo "ToSendProcess: maill() call completed\n"; } else { // Options parsing failed, use fallback with proper HTML detection and from-name $fallbackOptions = array(); // Detect HTML from message content $isHTML = (stripos($sMessage, '<html') !== false || stripos($sMessage, '<body') !== false || stripos($sMessage, '<div') !== false || stripos($sMessage, '<p') !== false); $fallbackOptions['isHTML'] = $isHTML; $fallbackOptions['content-type'] = $isHTML ? 'text/html' : 'text/plain'; // Use MessageFrom if available if (!empty($fromName)) { $fallbackOptions['from-name'] = trim($fromName, '\'"'); } if (!empty($fromAddress)) { $fallbackOptions['from-address'] = trim($fromAddress); $fallbackOptions['reply-to-address'] = trim($fromAddress); } // Set default from-name to "CESA" if not determined if (empty($fallbackOptions['from-name']) || trim($fallbackOptions['from-name']) === '') { $fallbackOptions['from-name'] = 'CESA'; ErrorLog::logInfo('ToSendProcess: Set default from-name to "CESA" in fallback options'); } // Add extracted attachments and embedded images to fallback options if (!empty($extractedAttachments)) { $fallbackOptions['attachments'] = $extractedAttachments; } if (!empty($embeddedImages)) { $fallbackOptions['embedded_images'] = $embeddedImages; } // Final safety check: Ensure from-name is always set before calling maill() $fallbackOptions['from-name'] = trim((string)($fallbackOptions['from-name'] ?? ''), '\'"'); if (empty($fallbackOptions['from-name'])) { $fallbackOptions['from-name'] = 'CESA'; // ErrorLog::logInfo('ToSendProcess: Final safety check - Set from-name to "CESA" in fallback'); } // ErrorLog::logInfo('ToSendProcess: Options parsing FAILED - using fallback with isHTML: ' . ($isHTML ? 'true' : 'false') . ', from-name: "' . (isset($fallbackOptions['from-name']) ? $fallbackOptions['from-name'] : 'not set') . '"'); // ErrorLog::logInfo('ToSendProcess: Options JSON that failed: ' . substr($rsM['Options'], 0, 500)); maill($rsR["SendTo"], $rsM["Subject"], $sMessage, null, $fallbackOptions); } } elseif ($rsM['Headers'] != '') { // Use Headers field only if Options is not available // Extract only relevant headers, not delivery headers $cleanedHeaders = stripslashes($rsM['Headers']); // Extract From, Reply-To, and Content-Type from headers $sendHeaders = array(); // Extract From if (preg_match('/^From:\s*(.+)$/mi', $cleanedHeaders, $matches)) { $sendHeaders['from'] = trim($matches[1]); } // Extract Reply-To if (preg_match('/^Reply-To:\s*(.+)$/mi', $cleanedHeaders, $matches)) { $sendHeaders['reply-to'] = trim($matches[1]); } // Extract Content-Type to determine if HTML $isHTML = false; if (preg_match('/^Content-Type:\s*([^;]+)/mi', $cleanedHeaders, $matches)) { $contentType = trim($matches[1]); if (stripos($contentType, 'text/html') !== false) { $isHTML = true; } } // Build options from extracted headers $options = array( 'isHTML' => $isHTML, 'content-type' => $isHTML ? 'text/html' : 'text/plain' ); // Use MessageFrom field if available, otherwise use from headers if (!empty($fromName)) { $options['from-name'] = trim($fromName, '\'"'); } if (!empty($fromAddress)) { $options['from-address'] = $fromAddress; $options['reply-to-address'] = $fromAddress; } elseif (isset($sendHeaders['from'])) { $options['from-address'] = $sendHeaders['from']; } if (isset($sendHeaders['reply-to'])) { $options['reply-to-address'] = $sendHeaders['reply-to']; } // Clean from-name if set (remove quotes) // For bulk mails, we want to use the sender's name from the email, not a default if (isset($options['from-name'])) { $options['from-name'] = trim($options['from-name'], '\'"'); } // Add extracted attachments and embedded images if (!empty($extractedAttachments)) { if (!isset($options['attachments']) || !is_array($options['attachments'])) { $options['attachments'] = []; } $options['attachments'] = array_merge($options['attachments'], $extractedAttachments); } if (!empty($embeddedImages)) { $options['embedded_images'] = $embeddedImages; } // ErrorLog::logInfo('ToSendProcess: Sending email to ' . $rsR["SendTo"] . ' using extracted headers, isHTML: ' . ($isHTML ? 'true' : 'false')); // ErrorLog::logInfo('ToSendProcess: Message preview: ' . substr($sMessage, 0, 200)); maill($rsR["SendTo"], $rsM["Subject"], $sMessage, null, $options); } else { // Default fallback - detect HTML from message content $isHTML = (stripos($sMessage, '<html') !== false || stripos($sMessage, '<body') !== false || stripos($sMessage, '<div') !== false || stripos($sMessage, '<p') !== false); $defaultOptions = array( 'isHTML' => $isHTML, 'content-type' => $isHTML ? 'text/html' : 'text/plain' ); // Use MessageFrom field if available if (!empty($fromName)) { $defaultOptions['from-name'] = trim($fromName, '\'"'); } if (!empty($fromAddress)) { $defaultOptions['from-address'] = $fromAddress; $defaultOptions['reply-to-address'] = $fromAddress; } // Clean from-name if set (remove quotes) // For bulk mails, we want to use the sender's name from the email, not a default if (isset($defaultOptions['from-name'])) { $defaultOptions['from-name'] = trim($defaultOptions['from-name'], '\'"'); } // Add extracted attachments and embedded images to default options if (!empty($extractedAttachments)) { $defaultOptions['attachments'] = $extractedAttachments; } if (!empty($embeddedImages)) { $defaultOptions['embedded_images'] = $embeddedImages; } // ErrorLog::logInfo('ToSendProcess: Default options being sent to maill() - from-name: "' . (isset($defaultOptions['from-name']) ? $defaultOptions['from-name'] : 'not set') . '", from-address: "' . (isset($defaultOptions['from-address']) ? $defaultOptions['from-address'] : 'not set') . '", isHTML: ' . ($defaultOptions['isHTML'] ? 'true' : 'false') . ', content-type: ' . (isset($defaultOptions['content-type']) ? $defaultOptions['content-type'] : 'not set')); // ErrorLog::logInfo('ToSendProcess: Sending email to ' . $rsR["SendTo"] . ' with subject: ' . $rsM["Subject"]); // ErrorLog::logInfo('ToSendProcess: Message preview: ' . substr($sMessage, 0, 200)); maill($rsR["SendTo"], $rsM["Subject"], $sMessage, null, $defaultOptions); } $iMsgsSent++; $bSomethingSent = true; // Update ToSend record to mark as sent if (isset($rsR["ToSendID"]) && !empty($rsR["ToSendID"])) { $sql = "UPDATE ToSend SET Sent=1 WHERE ToSendID=" . intval($rsR["ToSendID"]); $updateResult = mysqli_query($db, $sql); if (!$updateResult) { ErrorLog::logError('ToSendProcess: Failed to update ToSend record ToSendID=' . $rsR["ToSendID"] . ' - Error: ' . mysqli_error($db)); echo "ToSendProcess: ERROR - Failed to update ToSend record ToSendID=" . $rsR["ToSendID"] . "\n"; } else { echo "ToSendProcess: Updated ToSend record ToSendID=" . $rsR["ToSendID"] . " to Sent=1\n"; } } else { ErrorLog::logError('ToSendProcess: ToSendID not found in result set for SendTo=' . $rsR["SendTo"]); echo "ToSendProcess: ERROR - ToSendID not found for SendTo=" . $rsR["SendTo"] . "\n"; } echo $rsR["SendTo"] . "<BR>\r\n"; echo "."; flush(); ob_flush(); //wait for a quarter second //usleep(100000); } mysqli_free_result($rsRec); // Get school mailing list messages /* $sql = "SELECT sml_message_archive.subject AS Subject, sml_message_archive.message AS Message, sml_message_archive.id AS MessageID, sml_delivery_queue.email AS SendTo FROM sml_message_archive, sml_delivery_queue WHERE sml_message_archive.id=sml_delivery_queue.message_id AND sml_message_archive.start_send<=UNIX_TIMESTAMP()"; $rsMsg = mysql_query($sql); echo "."; flush(); ob_flush(); while (($iMsgsSent <= $maxmsgs) && ($rsM = mysql_fetch_assoc($rsMsg))) { $bSendFinished = false; $sql = "DELETE FROM sml_delivery_queue WHERE message_id=".$rsM["MessageID"]." AND email='".$rsM["SendTo"]."'"; mysql_query($sql); mail(stripslashes($rsM["SendTo"]), $rsM["Subject"], $rsM["Message"], "MIME-Version: 1.0" . "\r\n"."Content-type:text/html;charset=UTF-8" . "\r\n"."From: brenda@cesa.co.za"."\r\n"."Reply-To: brenda@cesa.co.za"."\r\n"); $iMsgsSent++; $bSomethingSent = true; $sql = "UPDATE sml_message_archive SET recipients=recipients+1 WHERE id=".$rsM["MessageID"]; mysql_query($sql); echo $rsM["SendTo"]."<BR>"; echo "."; flush(); ob_flush(); usleep(100000); } */ //if ($bSomethingSent) { $sql = "UPDATE LastSend SET EndSend='" . date("Y-m-d H:i:s") . "'"; mysqli_query($db, $sql); } //} } else { echo "Can't send yet."; } $sql = "DELETE FROM ToSend WHERE Sent=1 AND MessageID IN " . "(SELECT MessageID FROM Messages WHERE DateSent <= DATE_SUB(NOW(), INTERVAL 21 DAY))"; mysqli_query($db, $sql); $sql = "SELECT Messages.MessageID, ( SELECT COUNT(ToSend.ToSendID) AS Num FROM ToSend WHERE ToSend.MessageID = Messages.MessageID AND ToSend.Sent = 0) AS Num FROM Messages WHERE SendComplete=0 GROUP BY Messages.MessageID;"; echo $sql . "\r\n"; $rsRec = mysqli_query($db, $sql); while ($rsRow = mysqli_fetch_assoc($rsRec)) { if ($rsRow["Num"] == 0) { $sql = "UPDATE Messages SET SendComplete=1, DateComplete='" . date("Y-m-d H:i:s") . "' WHERE MessageID='" . $rsRow["MessageID"] . "'"; echo $sql . "\r\n"; mysqli_query($db, $sql); } } flush(); ob_flush(); } catch (Exception $ex) { unlink($lockFile); maill('cesa-bulk-mail@igotafrica.com', 'CESA Mailing List Error', 'Sending messages failed: ' . $ex->getMessage() . ' - ' . $ex->getTraceAsString()); } if (isset($db) && is_object($db)) { mysqli_close($db); } if (isset($conni) && is_object($conni)) { mysqli_close($conni); } unlink($lockFile);