From 3873cbf09a0091ae43bed49cad20445319ce7ec5 Mon Sep 17 00:00:00 2001 From: CamilleBeau Date: Wed, 18 Feb 2026 11:00:38 -0500 Subject: [PATCH 1/2] [Email] Add attachment functionality --- php/libraries/Email.class.inc | 52 +++++++++++++++++++++++++++++++++-- 1 file changed, 49 insertions(+), 3 deletions(-) diff --git a/php/libraries/Email.class.inc b/php/libraries/Email.class.inc index de9f8b78df..92b293b79f 100644 --- a/php/libraries/Email.class.inc +++ b/php/libraries/Email.class.inc @@ -55,7 +55,8 @@ class Email string $from = '', string $cc = '', string $bcc = '', - string $type="text/plain" + string $type="text/plain", + array $atachments = [] ): bool { $config =& NDB_Config::singleton(); $defaults = $config->getSetting('mail'); @@ -69,7 +70,6 @@ class Email } // build header $headers = "MIME-Version: 1.0\n"; - $headers .= "Content-type: $type; charset = UTF-8\n"; $headers .= "Reply-to: $reply_to\n"; $headers .= "From: $from\n"; $headers .= "Return-Path: $from\n"; @@ -105,11 +105,57 @@ class Email // get rid of the subject from the message body $message = preg_replace('/^Subject: .*/', '', $message); + // Set attachment settings in body if there are any + if (!empty($attachments)) { + + // boundary to seaprate MIME parts + $boundary = bin2hex(random_bytes(16)); + + // For attachments, use multipart + $headers .= "Content-Type: multipart/mixed; boundary=\"$boundary\"\r\n"; + + // In first part add charset / encoding since content-type in header + // is indicating multi-part + $body = "--$boundary\r\n"; + $body .= "Content-Type: $type; charset=UTF-8\r\n"; + $body .= "Content-Transfer-Encoding: 8bit\r\n\r\n"; + + // Add email message + $body .= $message . "\r\n"; + + foreach ($attachments as $file) { + + if (!file_exists($file['path'])) { + continue; + } + + // Use chunk_split for proper line length + $fileContent = chunk_split( + base64_encode(file_get_contents($file['path'])) + ); + + $filename = $file['name'] ?? basename($file['path']); + $filetype = $file['type'] ?? 'application/octet-stream'; + + // Add part for each file attachment + $body .= "--$boundary\r\n"; + $body .= "Content-Type: $filetype; name=\"$filename\"\r\n"; + $body .= "Content-Disposition: attachment; filename=\"$filename\"\r\n"; + $body .= "Content-Transfer-Encoding: base64\r\n\r\n"; + $body .= $fileContent . "\r\n"; + } + + $body .= "--$boundary--"; + } else { + $headers .= "Content-type: $type; charset=UTF-8\r\n"; + $body = $message; + } + // send the email return mail( $to, $match[1], - preg_replace("/(? Date: Mon, 2 Mar 2026 08:39:36 -0500 Subject: [PATCH 2/2] Dave's comments --- php/libraries/Email.class.inc | 48 +++++++++++++++++++++-------------- 1 file changed, 29 insertions(+), 19 deletions(-) diff --git a/php/libraries/Email.class.inc b/php/libraries/Email.class.inc index 92b293b79f..a38cb831ff 100644 --- a/php/libraries/Email.class.inc +++ b/php/libraries/Email.class.inc @@ -35,14 +35,15 @@ class Email * Every template MUST BEGIN with "Subject: subject text". Put new * templates in the subfolder "email" of the Smarty templates folder. * - * @param string $to email address to send email to - * @param string $template template to use for email content - * @param array $tpl_data template's data for smarty binding - * @param string $reply_to optional email header - * @param string $from optional email header - * @param string $cc optional email header - * @param string $bcc optional email header - * @param string $type optional defaults to plain text + * @param string $to email address to send email to + * @param string $template template to use for email content + * @param array $tpl_data template's data for smarty binding + * @param string $reply_to optional email header + * @param string $from optional email header + * @param string $cc optional email header + * @param string $bcc optional email header + * @param string $type optional defaults to plain text + * @param array $attachments optional files to attach * * @return bool The result of PHP mail(). * @access public @@ -56,7 +57,7 @@ class Email string $cc = '', string $bcc = '', string $type="text/plain", - array $atachments = [] + array $attachments = [] ): bool { $config =& NDB_Config::singleton(); $defaults = $config->getSetting('mail'); @@ -111,7 +112,7 @@ class Email // boundary to seaprate MIME parts $boundary = bin2hex(random_bytes(16)); - // For attachments, use multipart + // For attachments, use multipart $headers .= "Content-Type: multipart/mixed; boundary=\"$boundary\"\r\n"; // In first part add charset / encoding since content-type in header @@ -124,23 +125,32 @@ class Email $body .= $message . "\r\n"; foreach ($attachments as $file) { - - if (!file_exists($file['path'])) { - continue; + if (!$file instanceof \SplFileInfo) { + throw new \Exception( + "Invalid attachment provided. " . + "Expected instance of SplFileInfo." + ); + } + $filePath = $file->getRealPath(); + $filename = $file->getFilename(); + $filetype = mime_content_type($filePath); + + if (!file_exists($filePath)) { + throw new \Exception( + "Email attachment file not found: {$filePath}" + ); } // Use chunk_split for proper line length $fileContent = chunk_split( - base64_encode(file_get_contents($file['path'])) + base64_encode(file_get_contents($filePath)) ); - $filename = $file['name'] ?? basename($file['path']); - $filetype = $file['type'] ?? 'application/octet-stream'; - // Add part for each file attachment $body .= "--$boundary\r\n"; $body .= "Content-Type: $filetype; name=\"$filename\"\r\n"; - $body .= "Content-Disposition: attachment; filename=\"$filename\"\r\n"; + $body .= + "Content-Disposition: attachment; filename=\"$filename\"\r\n"; $body .= "Content-Transfer-Encoding: base64\r\n\r\n"; $body .= $fileContent . "\r\n"; } @@ -148,7 +158,7 @@ class Email $body .= "--$boundary--"; } else { $headers .= "Content-type: $type; charset=UTF-8\r\n"; - $body = $message; + $body = $message; } // send the email