<?php

    
/**
    * Puggans SMTP/email-class
    * Allows easy sending of emails, both plain text, HTML and
    * mixed messages with alternate content and attachments.
    **/
    
class smtp
    
{
        public 
$hostname;
        public 
$default_from;
        public 
$default_charset;

        function 
__construct($hostname NULL$default_from 'noreply'$default_charset 'ISO-8859-1')
        {
            
// Guess the hostname if missing
            
if(!$hostname AND isset($_SERVER['HTTP_HOST'])) $hostname $_SERVER['HTTP_HOST'];
            if(!
$hostname$hostname gethostname();
            if(!
$hostname$hostname 'localhost';

            
// save default variables
            
$this->hostname $hostname;
            
$this->default_from $default_from;
            
$this->default_charset $default_charset;
        }

        
/**
        * Creates a message with attachments and sends it as plain text.
        **/
        
public function send_plain($subject$body$to$from NULL$attachments NULL$charset NULL$extra_headers NULL$private_key NULL$public_key NULL)
        {
            return 
$this->send($subjectNULL$body$to$from$attachments$charset$extra_headers$private_key$public_key);
        }

        
/**
        * Creates a message with attachments and sends it as HTML.
        **/
        
public function send_html($subject$body$to$from NULL$attachments NULL$charset NULL$extra_headers NULL$private_key NULL$public_key NULL)
        {
            return 
$this->send($subject$bodyNULL$to$from$attachments$charset$extra_headers$extra_headers,$private_key$public_key);
        }

        public function 
send($subject$html_body$text_body$to$from NULL$attachments NULL$charset NULL$extra_headers NULL$private_key NULL$public_key NULL)
        {
            
$parts $this->create_email($subject$html_body$text_body$to$from$attachments$charset$extra_headers$private_key$public_key);

            if(
$parts)
            {
                
/* Send the actual message. */
                
return mail($parts['to'], $parts['subject'], $parts['body'], $this->flaten_headers($parts['headers']));
            }
            else
            {
                return 
FALSE;
            }
        }

        public function 
create_email($subject$html_body$text_body$to$from NULL$attachments NULL$charset NULL$extra_headers NULL$private_key NULL$public_key NULL)
        {
            
// set default vars if missing
            
if(!$charset$charset $this->default_charset;
            if(!
$from$from $this->default_from;
            if(!
strstr($from'@')) $from .= '@' $this->hostname;

            
// remeber what encoding we had, in case we need to change it
            
$old_internal_encoding mb_internal_encoding();

            if(
$html_body)
            {
                
// FIXME: build support for CID, by sending an array as $html_body
                
$encoding mb_detect_encoding($html_body'ASCII, UTF-8, ISO-8859-1'TRUE);
                
$html_body mb_convert_encoding($html_body$charset$encoding);

                if(!
stristr($html_body'<html'))
                {
                    
$html_body = <<<HTML_BLOCK
<html>
    <head>
        <title>
{$subject}</title>
        <meta http-equiv="content-type" content="text/html; charset=
{$charset}" />
    </head>
    <body>
{$html_body}
    </body>
</html>
HTML_BLOCK;
                }
            }

            if(
$text_body)
            {
                
$encoding_text mb_detect_encoding($text_body'ASCII, UTF-8, ISO-8859-1'TRUE);
                
$text_body mb_convert_encoding($text_body$charset$encoding_text);
            }

            
/* if subject is none ascii, mime encode it */
            
if(!mb_detect_encoding($subject"ASCII"TRUE))
            {
                
$subject_encoding mb_detect_encoding($subject'ASCII, UTF-8, ISO-8859-1'TRUE);
                
mb_internal_encoding($subject_encoding);
                
$subject mb_encode_mimeheader($subject$subject_encoding'Q');
                
mb_internal_encoding($old_internal_encoding);
            }

            
// check if none-ascii in from-field
            
if(!mb_detect_encoding($from"ASCII"TRUE))
            {
                
$from_parts explode("<"$from);
                if(
count($from_parts) == 2)
                {
                    
$from_encoding mb_detect_encoding($from'ASCII, UTF-8, ISO-8859-1'TRUE);
                    
mb_internal_encoding($from_encoding);
                    
$from mb_encode_mimeheader($from_parts[0], $from_encoding'Q') . "<" $from_parts[1];
                    
mb_internal_encoding($old_internal_encoding);
                }
            }

            
// check if none-ascii in from-field
            
if(!mb_detect_encoding($to"ASCII"TRUE))
            {
                
$to_parts explode("<"$to);
                if(
count($to_parts) == 2)
                {
                    
$to_encoding mb_detect_encoding($to'ASCII, UTF-8, ISO-8859-1'TRUE);
                    
mb_internal_encoding($to_encoding);
                    
$to mb_encode_mimeheader($to_parts[0], $to_encoding'Q') . "<" $to_parts[1];
                    
mb_internal_encoding($old_internal_encoding);
                }
            }

            
$base_headers = array();
            
$part_headers = array();
            
$email_data "";

            if(!
is_array($extra_headers)) $extra_headers = array();

            
/* Set the mail client field. */
            
$base_headers['X-Mailer'] = (isset($extra_headers['X-Mailer']) ? $extra_headers['X-Mailer'] : "X-Mailer: Puggan-smtp v0.1");
            
$base_headers['Date'] = (isset($extra_headers['Date']) ? $extra_headers['Date'] : "Date: " date("r"));
            
$base_headers['Message-ID'] = (isset($extra_headers['Message-ID']) ? $extra_headers['Message-ID'] : "Message-ID: <" $this->generate_hash() . "@{$this->hostname}>");
            
$base_headers['Subject'] = (isset($extra_headers['Subject']) ? $extra_headers['Subject'] : "Subject: {$subject}");

            
/* Set the SMTP headers for recipient and senders. */
            
$base_headers['Sender'] = (isset($extra_headers['Sender']) ? $extra_headers['Sender'] : "Sender: {$from}");
            
$base_headers['From'] = (isset($extra_headers['From']) ? $extra_headers['From'] : "From: {$from}");
            
$base_headers['Reply-To'] = (isset($extra_headers['Reply-To']) ? $extra_headers['Reply-To'] : "Reply-To: {$from}");
            
$base_headers['To'] = (isset($extra_headers['To']) ? $extra_headers['To'] : "To: {$to}");

            
$base_headers += $extra_headers;

            
$content_type "text/plain";
            
$part_headers = array();
            
$part_headers['Content-Type'] = "Content-Type: {$content_type}; charset={$charset}";
            
$part_headers['Content-Transfer-Encoding'] = "Content-Transfer-Encoding: 8bit";
            
$email_data $text_body;

            if(
$html_body)
            {
                if(
$email_data)
                {
                    
$parts = array();

                    if(
$content_type == "text/plain")
                    {
                        
$parts['html'] = "";
                    }

                    
$parts['txt'] = $this->flaten_email($part_headers$email_data);

                    
$part_headers = array();
                    
$part_headers['Content-Type'] = "Content-Type: text/html; charset={$charset}";
                    
$part_headers['Content-Transfer-Encoding'] = "Content-Transfer-Encoding: 8bit";

                    if(
$private_key)
                    {
                        
$part_headers['Content-Transfer-Encoding'] = "Content-Transfer-Encoding: base64";

                        
$parts['html'] = $this->flaten_email($part_headerschunk_split(base64_encode($html_body)));
                    }
                    else
                    {
                        
$parts['html'] = $this->flaten_email($part_headers$html_body);
                    }

                    
$parts $this->mime_encode('multipart/alternative'$partsTRUE);

                    
$content_type "multipart/alternative";
                    
$part_headers $parts['headers'];
                    
$email_data $parts['body'];
                    unset(
$parts);
                }
                else
                {
                    
$email_data $html_body;
                    
$content_type "text/html";
                    
$part_headers = array('Content-Type' => "Content-Type: {$content_type}; charset={$charset}""Content-Transfer-Encoding" => "Content-Transfer-Encoding: 8bit");
                }
            }

            if(
$attachments)
            {
                
$parts = array();
                
$parts[] = implode("\n"$part_headers) . "\n\n" $email_data;

                
/* Iterate over each file attachment. */
                
foreach((array) $attachments as $row_id => $attachment)
                {
                    
$part_headers = array();
                    
$current_part "";

                    
/* If the attachment is a file.. */
                    
if(is_array($attachment))
                    {
                        
$part_headers['Content-Type'] = "Content-Type: {$attachment['file_mime_type']}; name=\"{$attachment['file_name']}\"";
                        
$part_headers['Content-Disposition'] = "Content-Disposition: attachment; filename=\"{$attachment['file_name']}\"";
                        
$part_headers['Content-Transfer-Encoding'] = "Content-Transfer-Encoding: base64";

                        
$parts[] = implode("\n"$part_headers) . "\n\n" chunk_split(base64_encode($attachment['data']));
                    }
                    else if(
file_exists($attachment))
                    {
                        
$file_info finfo_open(FILEINFO_MIME);

                        
/* Read the file mime type, name etc. */
                        
$file_mime_type finfo_file($file_info$attachment);
                        
$file_name basename($attachment);

                        
finfo_close($file_info);

                        
$part_headers['Content-Type'] = "Content-Type: {$file_mime_type}; name=\"{$file_name}\"";
                        
$part_headers['Content-Disposition'] = "Content-Disposition: attachment; filename=\"{$file_name}\"";
                        
$part_headers['Content-Transfer-Encoding'] = "Content-Transfer-Encoding: base64";

                        
$parts[] = implode("\n"$part_headers) . "\n\n" chunk_split(base64_encode(file_get_contents($attachment)));
                    }
                    else
                    {
                        
/* Read the file mime type, name etc. */
                        
$file_mime_type "text/plain";
                        
$file_name "attachment_{$row_id}.txt";

                        
$part_headers['Content-Type'] = "Content-Type: {$file_mime_type}; name=\"{$file_name}\"";
                        
$part_headers['Content-Disposition'] = "Content-Disposition: attachment; filename=\"{$file_name}\"";
                        
$part_headers['Content-Transfer-Encoding'] = "Content-Transfer-Encoding: base64";

                        
$parts[] = implode("\n"$part_headers) . "\n\n" chunk_split(base64_encode($attachment));
                    }
                }

                
$parts $this->mime_encode('multipart/mixed'$partsTRUE);
                
$content_type "multipart/mixed";
                
$part_headers $parts['headers'];
                
$email_data $parts['body'];
                unset(
$parts);
            }

            if(
$private_key)
            {
                if(!
class_exists("gnupg"))
                {
                    
trigger_error("Missing package: dev-php/pecl-gnupg");
                    return 
FALSE;
                }

                
$gpg = new gnupg();
                if(
file_exists($private_key))
                {
                    
$key_info $gpg->import(file_get_contents($private_key));
                }
                else
                {
                    
$key_info $gpg->import($private_key);
                }

                if(!
$key_info)
                {
                    
trigger_error("Failed to load private key");
                    return 
FALSE;
                }

                
$result $gpg->addsignkey($key_info["fingerprint"]);
                if(!
$result)
                {
                    
trigger_error("Failed selecting private key");
                    return 
FALSE;
                }

                if(!
$email_data AND $html_body)
                {
                    
$email_data strip_tags($html_body);
                }

                
$signature_parts = array();

                
$signature_parts[0] = $this->flaten_email($part_headers$email_data);

                
$gpg->setsignmode(GNUPG_SIG_MODE_DETACH);

                
$signature $gpg->sign($signature_parts[0]);
                
$signature_headers = array();
                
$signature_headers['Content-Type'] = 'Content-Type: application/pgp-signature; name="signature.asc"';
                
$signature_headers['Content-Description'] = 'Content-Description: This is a digitally signed message part.';
                
$signature_headers['Content-Transfer-Encoding'] = 'Content-Transfer-Encoding: 7Bit';
                
$signature_parts[1] = $this->flaten_email($signature_headers$signature);

                
$parts $this->mime_encode('multipart/signed; micalg="pgp-sha1"; protocol="application/pgp-signature"'$signature_partsTRUE);

                
$content_type "multipart/signed";
                
$part_headers $parts['headers'];
                
$email_data $parts['body'];
                unset(
$parts);
            }

            if(
$public_key)
            {
                
// FIXME: encrypt message using public key
            
}

            return array(
                
'from' => $from,
                
'to' => $to,
                
'subject' => $subject,
                
'content_type' => $content_type,
                
'headers' => $base_headers $part_headers,
                
'body' => $this->flaten_body($email_data),
            );
        }

        function 
flaten_email($headers$body)
        {
            return 
$this->flaten_headers($headers) . "\r\n\r\n" $this->flaten_body($body);
        }

        function 
flaten_headers($headers)
        {
            if(
is_array($headers))
            {
                return 
implode("\r\n"$headers);
            }
            else
            {
                
print_r(debug_backtrace());
                
var_dump($headers);
                die();
            }
        }

        function 
flaten_body($body)
        {
            return 
str_replace("\n","\r\n"str_replace("\r"""$body));
        }

        function 
mime_encode($mime_type$parts$headers NULL)
        {
            
$new_headers = array();
            
$body '';
            
$boundary $this->generate_hash();

            
$new_headers['MIME-Version'] = "MIME-Version: 1.0";
            
$new_headers['Content-Type'] = "Content-Type: {$mime_type}; boundary=\"{$boundary}\"";

            foreach(
$parts as $current_part)
            {
                
$body .= "--{$boundary}\n{$current_part}\n";
            }
            
$body .= "--{$boundary}--\n";

            if(
$headers === TRUE)
            {
                return array(
'boundary' => $boundary'headers' => $new_headers'body' => $body);
            }
            else if(
$headers === FALSE)
            {
                return 
$body;
            }
            else if(
is_array($headers))
            {
                return 
$this->flaten_email($headers $new_headers$body);
            }
            else if(
$headers)
            {
                return 
trim(trim($headers) . "\r\n"$this->flaten_email($new_headers$body));
            }
            else
            {
                return 
$this->flaten_email($new_headers$body);
            }
        }

        function 
generate_hash($length 32)
        {
            if(
$length 1)
            {
                return 
"";
            }

            
$hash md5(uniqid(microtime()));
            
$current_length strlen($hash);

            if(
$current_length == $length)
            {
                return 
$hash;
            }

            if(
$current_length $length)
            {
                return 
substr($hash0$length);
            }

            return 
$hash $this->generate_hash($length $current_length);
        }
    }

?>