mcramer
2012-09-21 b0191fc04e7ea04d08a42e67c184c9be704ec12a
commit | author | age
a59498 1 <?php
M 2
3 /*
4 Copyright (c) 2012, Marius Cramer, pixcept KG
5 All rights reserved.
6
7 Redistribution and use in source and binary forms, with or without modification,
8 are permitted provided that the following conditions are met:
9
10     * Redistributions of source code must retain the above copyright notice,
11       this list of conditions and the following disclaimer.
12     * Redistributions in binary form must reproduce the above copyright notice,
13       this list of conditions and the following disclaimer in the documentation
14       and/or other materials provided with the distribution.
15     * Neither the name of ISPConfig nor the names of its contributors
16       may be used to endorse or promote products derived from this software without
17       specific prior written permission.
18
19 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
20 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
22 IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
23 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
24 BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
26 OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
27 NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
28 EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31 /**
32  * email class
33  * 
34  * @package pxFramework
35  *
36  */
37 class ispcmail {
38     
39     /**#@+
40      * @access private
41      */
42     private $html_part;
43     private $text_part;
44     
45     private $headers;
46     
47     private $_logged_in = false;
48     private $_smtp_conn = null;
49     
50     private $_crlf = "\n";
51     
52     private $attach_type = 'application/octet-stream';
53     private $attachments;
54     private $mime_boundary;
55     private $body = '';
56     private $_mail_sender = '';
57     /**#@-*/
58     
59     /**
60      * set the mail charset
61      */
62     private $mail_charset  = 'UTF-8';//'ISO-8859-1';
63     
64     /**#@+
65      * Provide smtp credentials for smtp mail sending
66      *
67      * @access public
68      */
69     /**
70      * if set to true smtp is used instead of mail() to send emails
71      * @see mail
72      */
73     private $use_smtp = false;
74     /**
75      * the smtp helo string - use the mail server name here!
76      */
77     private $smtp_helo = '';
78     /**
79      * the smtp server to send mails
80      */
81     private $smtp_host = '';
82     /**
83      * the smtp port
84      */
85     private $smtp_port = 25;
86     /**
87      * if the smtp server needs authentication you can set the smtp user here
88      */
89     private $smtp_user = '';
90     /**
91      * if the smtp server needs authentication you can set the smtp password here
92      */
93     private $smtp_pass = '';
94     /**
95      * If you want to use tls/ssl specify it here
96      */
97     private $smtp_crypt = ''; // tls or ssl
98     /**#@-*/
99     
100     public function __construct($options = array()) {
101         $rand = md5(microtime());
102         $this->mime_boundary = '==Multipart_Boundary_x' . $rand . 'x';
103         
104         $this->headers = array();
105         $this->attachments = array();
106         
107         $this->headers['MIME-Version'] = '1.0';
108         $this->setOptions($options);
109     }
110     
111     public function __destruct() {
112         $this->finish();
113     }
114     
115     /**
116      * Set option
117      * 
118      * @param string $key the option to set
119      * @param string $value the option value to set
120      */
121     public function setOption($key, $value) {
122         switch($key) {
123             case 'smtp_helo':
124                 $this->smtp_helo = $value;
125                 break;
126             case 'smtp_host':
127                 $this->smtp_host = $value;
128                 break;
129             case 'smtp_server':
130                 $this->smtp_host = $value;
131                 break;
132             case 'smtp_port':
133                 $this->smtp_port = $value;
134                 break;
135             case 'smtp_user':
136                 $this->smtp_user = $value;
137                 break;
138             case 'smtp_pass':
139                 $this->smtp_pass = $value;
140                 break;
141             case 'use_smtp':
142                 $this->use_smtp = ($value == true ? true : false);
143                 if($value == true) $this->_crlf = "\r\n";
144                 break;
145             case 'smtp_crypt':
146                 if($value != 'ssl' && $value != 'tls') $value = '';
147                 $this->smtp_crypt = $value;
148                 break;
149             case 'mail_charset':
150                 $this->mail_charset = $value;
151                 break;
152         }
153     }
154     
155     private function detectHelo() {
156         if(isset($_SERVER['SERVER_NAME'])) $this->smtp_helo = $_SERVER['SERVER_NAME'];
157         else $this->smtp_helo = php_uname('n');
158         if($this->smtp_helo == '') $this->smtp_helo = 'localhost';
159     }
160     
161     /**
162      * Set options
163      * 
164      * @param array $options the options to set as an associative array key => value
165      */
166     public function setOptions($options) {
167         foreach($options as $key => $value) $this->setOption($key, $value);
168     }
169     
170     /**
171      * Read a file's contents
172      *
173      * Simply gets the file's content
174      *
175      * @access public
176      * @param string $filename name and path of file to read
177      * @return string file content (can be binary)
178      */
179     public function read_File($filename) {
180         $content = '';
181         
182         $fp = fopen($filename, 'r');
183         if(!$fp) return false;
184         
185         while(!feof($fp)) {
186             $content .= fread($fp, 1024);
187         }
188         fclose($fp);
189         
190         return $content;
191     }
192     
193     /**
194      * set smtp connection encryption
195      * 
196      * @access public
197      * @param string $mode encryption mode (tls, ssl or empty string)
198      */
199     public function setSMTPEncryption($mode = '') {
200         if($mode != 'ssl' && $mode != 'tls') $mode = '';
201         $this->smtp_crypt = $mode;
202     }
203
204     /**
205      * set a mail header
206      *
207      * Sets a single mail header to a given value
208      *
209      * @access public
210      * @param string $header header name to set
211      * @param string $value value to set in header field
212      */
213     public function setHeader($header, $value) {
214         $this->headers["$header"] = $value;
215     }
216     
217     /**
218      * get a mail header value
219      *
220      * Returns a value of a single mail header
221      *
222      * @access public
223      * @param string $header header name to get
224      * @return string header value
225      */
226     public function getHeader($header) {
227         return $this->headers["$header"];
228     }
229     
230     /**
231      * Set email sender
232      *
233      * Sets the email sender and optionally the sender's name
234      *
235      * @access public
236      * @param string $email sender email address
237      * @param string $name sender name
238      */
239     public function setSender($email, $name = '') {
240         if($name) $header = '"' . $name . '" <' . $email . '>';
241         else $header = '<' . $email . '>';
242         
243         $this->_mail_sender = $email;
244         
245         $this->setHeader('From', $header);
246     }
247     
248     /**
249      * Set mail subject
250      *
251      * @access public
252      * @param string $subject the mail subject
253      * @return string where-string for db query
254      */
255     public function setSubject($subject) {
256         $this->setHeader('Subject', $subject);
257     }
258     
259     /**
260      * Get current mail subject
261      *
262      * @access public
263      * @return string mail subject
264      */
265     public function getSubject() {
266         return $this->headers['Subject'];
267     }
268     
269     /**
270      * Set mail content
271      *
272      * Sets the mail html and plain text content
273      *
274      * @access public
275      * @param string $text plain text mail content (can be empty)
276      * @param string $html html mail content
277      */
278     public function setMailText($text, $html = '') {
279         $this->text_part = $text;
280         $this->html_part = $html;
281     }
282     
283     /**
284      * Read and attach a file
285      *
286      * Reads a file and attaches it to the current email
287      *
288      * @access public
289      * @param string $filename the file to read and attach
290      * @param string $display_name the name that will be displayed in the mail
291      * @see read_File
292      */
293     public function readAttachFile($filename, $display_name = '') {
294         if($display_name == '') {
295             $path_parts = pathinfo($filename);
296             $display_name = $path_parts["basename"];
297             unset($path_parts);
298         }
299         $this->attachFile($this->read_File($filename), $display_name);
300     }
301     
302     /**
303      * Attach a file
304      *
305      * Attaches a string (can be binary) as a file to the mail
306      *
307      * @access public
308      * @param string $content attachment data string
309      * @param string $filename name for file attachment
310      */
311     public function attachFile($content, $filename) {
312         $attachment = array('content' => $content,
313                             'filename' => $filename,
314                             'type' => 'application/octet-stream',
315                             'encoding' => 'base64'
316                            );
317         $this->attachments[] = $attachment;
318     }
319     
320     /**
321      * @access private
322      */
323     private function create() {
324         $attach = false;
325         $html = false;
326         $text = false;
327         
328         if($this->html_part) $html = true;
329         if($this->text_part) $text = true;
330         if(count($this->attachments) > 0) $attach = true;
331         
332         $textonly = false;
333         $htmlonly = false;
334         if($text == true && $html == false && $attach == false) {
335             // only text
336             $content_type = 'text/plain; charset="' . strtolower($this->mail_charset) . '"';
337             $textonly = true;
338         } elseif($text == true && $html == false && $attach == true) {
339             // text and attachment
340             $content_type = 'multipart/mixed;';
341             $content_type .= "\n" . ' boundary="' . $this->mime_boundary . '"';
342         } elseif($html == true && $text == true && $attach == false) {
343             // html only (or text too)
344             $content_type = 'multipart/alternative;';
345             $content_type .= "\n" . ' boundary="' . $this->mime_boundary . '"';
346         } elseif($html == true && $text == false && $attach == false) {
347             // html only (or text too)
348             $content_type = 'text/html; charset="' . strtolower($this->mail_charset) . '"';
349             $htmlonly = true;
350         } elseif($html == true && $attach == true) {
351             // html and attachments
352             $content_type = 'multipart/mixed;';
353             $content_type .= "\n" . ' boundary="' . $this->mime_boundary . '"';
354         }
355         
356         $this->headers['Content-Type'] = $content_type;
357         
358         if($textonly == false && $htmlonly == false) {
359             $this->body = "This is a multi-part message in MIME format.\n\n";
360             
361             if($text) {
362                 $this->body .= "--{$this->mime_boundary}\n" .
363                               "Content-Type:text/plain; charset=\"" . strtolower($this->mail_charset) . "\"\n" .
364                               "Content-Transfer-Encoding: 7bit\n\n" . $this->text_part . "\n\n";
365             }
366             
367             if($html) {
368                 $this->body .= "--{$this->mime_boundary}\n" .
369                                "Content-Type:text/html; charset=\"" . strtolower($this->mail_charset) . "\"\n" . 
370                                "Content-Transfer-Encoding: 7bit\n\n" . $this->html_part . "\n\n";
371             }
372             
373             if($attach) {
374                 foreach($this->attachments as $att) {
375                     $this->body .= "--{$this->mime_boundary}\n" .
376                                    "Content-Type: " . $att['type'] . ";\n" .
377                                    " name=\"" . $att['filename'] . "\"\n" .
378                                    "Content-Transfer-Encoding: base64\n\n" .
379                                    chunk_split(base64_encode($att['content'])) . "\n\n";
380                 }
381             }
382             $this->body .= "--{$this->mime_boundary}--\n";
383         } elseif($htmlonly == true) {
384             $this->body = $this->html_part;
385         } else {
386             $this->body = $this->text_part;
387         }
388         
389         if (isset($this->body)) {
390             // Add message ID header
391             $message_id = sprintf('<%s.%s@%s>', base_convert(time(), 10, 36), base_convert(rand(), 10, 36), !empty($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : $_SERVER['SERVER_NAME']);
392             $this->headers['Message-ID'] = $message_id;
393             return true;
394         } else {
395             return false;
396         }
397     }
398     
399     /**
400     * Function to encode a header if necessary
401     * according to RFC2047
402     * @access private
403     */
404     private function _encodeHeader($input, $charset = 'ISO-8859-1') {
405         preg_match_all('/(\s?\w*[\x80-\xFF]+\w*\s?)/', $input, $matches);
406         foreach ($matches[1] as $value) {
407             $replacement = preg_replace('/([\x20\x80-\xFF])/e', '"=" . strtoupper(dechex(ord("\1")))', $value);
408             $input = str_replace($value, '=?' . $charset . '?Q?' . $replacement . '?=', $input);
409         }
410         
411         return $input;
412     }
413     
414     /**
415      * @access private
416      */
417     private function _smtp_login() {
418         $this->_smtp_conn = fsockopen(($this->smtp_crypt == 'ssl' ? 'ssl://' : '') . $this->smtp_host, $this->smtp_port, $errno, $errstr, 30);
419         $response = fgets($this->_smtp_conn, 515);
420         if(empty($this->_smtp_conn)) return false;
421         
422         // ENCRYPTED?
423         if($this->smtp_crypt == 'tls') {
424             fputs($this->_smtp_conn, 'STARTTLS' . $this->_crlf);
425             fgets($this->_smtp_conn, 515);
426             stream_socket_enable_crypto($this->_smtp_conn, true, STREAM_CRYPTO_METHOD_TLS_CLIENT);
427         }
428         
429         //Say Hello to SMTP
430         if($this->smtp_helo == '') $this->detectHelo();
431         fputs($this->_smtp_conn, 'HELO ' . $this->smtp_helo . $this->_crlf);
432         $response = fgets($this->_smtp_conn, 515);
433         
434         //AUTH LOGIN
435         fputs($this->_smtp_conn, 'AUTH LOGIN' . $this->_crlf);
436         $response = fgets($this->_smtp_conn, 515);
437         
438         //Send username
439         fputs($this->_smtp_conn, base64_encode($this->smtp_user) . $this->_crlf);
440         $response = fgets($this->_smtp_conn, 515);
441         
442         //Send password
443         fputs($this->_smtp_conn, base64_encode($this->smtp_pass) . $this->_crlf);
444         $response = fgets($this->_smtp_conn, 515);
445         
446         $this->_logged_in = true;
447         return true;
448     }
449     
450     /**
451      * @access private
452      */
453     private function _smtp_close() {
454         $this->_logged_in = false;
455         
456         if(empty($this->_smtp_conn)) {
457             return false;
458         }
459         
460         fputs($this->_smtp_conn, 'QUIT' . $this->_crlf);
461         $response = @fgets($this->_smtp_conn, 515);
462         return true;
463     }
464     
465     /**
466      * Send the mail to one or more recipients
467      *
468      * The recipients can be either a string (1 recipient email without name) or an associative array of recipients with names as keys and email addresses as values.
469      *
470      * @access public
471      * @param mixed $recipients one email address or array of recipients with names as keys and email addresses as values
472      */
473     public function send($recipients) {
474         if(!is_array($recipients)) $recipients = array($recipients);
475         
476         $this->create();
477         
478         $subject = '';
479         if (!empty($this->headers['Subject'])) {
480             //$subject = $this->_encodeHeader($this->headers['Subject'], $this->mail_charset);
481             $subject = $this->headers['Subject'];
482             
483             $enc_subject = $this->_encodeHeader($subject, $this->mail_charset);
484             unset($this->headers['Subject']);
485         }
486         
487         unset($this->headers['To']); // always reset the To header to prevent from sending to multiple users at once
488         $this->headers['Date'] = date('r'); //date('D, d M Y H:i:s O');
489
490         // Get flat representation of headers
491         foreach ($this->headers as $name => $value) {
492             if(strtolower($name) == 'to') continue; // never add the To header
493             $headers[] = $name . ': ' . $this->_encodeHeader($value, $this->mail_charset);
494         }
495         
496         if($this->use_smtp == true) {
497             if(!$this->_logged_in || !$this->_smtp_conn) {
498                 $result = $this->_smtp_login();
499                 if(!$result) return false;
500             }
501             foreach($recipients as $recipname => $recip) {
502                 $recipname = trim(str_replace('"', '', $recipname));
503                 $recip = $this->_encodeHeader($recip, $this->mail_charset);
504                 $recipname = $this->_encodeHeader($recipname, $this->mail_charset);
505                 
506                 //Email From
507                 fputs($this->_smtp_conn, 'MAIL FROM: ' . $this->_mail_sender . $this->_crlf);
508                 $response = fgets($this->_smtp_conn, 515);
509                 
510                 //Email To
511                 fputs($this->_smtp_conn, 'RCPT TO: ' . $recip . $this->_crlf);
512                 $response = fgets($this->_smtp_conn, 515);
513                 
514                 //The Email
515                 fputs($this->_smtp_conn, 'DATA' . $this->_crlf);
516                 $response = fgets($this->_smtp_conn, 515);
517                 
518                 //Construct Headers
519                 if($recipname && !is_numeric($recipname)) $this->setHeader('To', $recipname . ' <' . $recip . '>');
520                 else $this->setHeader('To', $recip);
521                 
522                 
523                 $mail_content = 'To: ' . $this->getHeader('To') . $this->_crlf . 'Subject: ' . $enc_subject . $this->_crlf;
524                 $mail_content .= implode($this->_crlf, $headers) . $this->_crlf . $this->_crlf . $this->body;
525                 
526                 fputs($this->_smtp_conn, $mail_content . $this->_crlf . '.' . $this->_crlf);
527                 $response = fgets($this->_smtp_conn, 515);
528                 
529                 // hopefully message was correctly sent now
530                 $result = true;
531             }
532         } else {
533             $rec_string = '';
534             foreach($recipients as $recipname => $recip) {
535                 $recipname = trim(str_replace('"', '', $recipname));
536                 
537                 if($rec_string != '') $rec_string .= ', ';
538                 if($recipname && !is_numeric($recipname)) $rec_string .= $recipname . '<' . $recip . '>';
539                 else $rec_string .= $recip;
540             }
541             $to = $this->_encodeHeader($rec_string, $this->mail_charset);
542             $result = mail($to, $subject, $this->body, implode($this->_crlf, $headers));
543         }
544         
545         // Reset the subject in case mail is resent
546         if ($subject !== '') {
547             $this->headers['Subject'] = $subject;
548         }
549         
550         // Return
551         return $result;
552     }
553     
554     /**
555      * Close mail connections
556      *
557      * This closes an open smtp connection so you should always call this function in your script if you have finished sending all emails
558      *
559      * @access public
560      */
561     public function finish() {
562         if($this->use_smtp == true) $this->_smtp_close();
b0191f 563         
M 564         $rand = md5(microtime());
565         $this->mime_boundary = '==Multipart_Boundary_x' . $rand . 'x';
566         
567         $this->headers = array();
568         $this->attachments = array();
569         $this->text_part = '';
570         $this->html_part = '';
571         
572         $this->headers['MIME-Version'] = '1.0';
573         
574         $this->smtp_helo = '';
575         $this->smtp_host = '';
576         $this->smtp_port = '';
577         $this->smtp_user = '';
578         $this->smtp_pass = '';
579         $this->use_smtp = false;
580         $this->smtp_crypt = false;
581         $this->mail_charset = 'UTF-8';
a59498 582         return;
M 583     }
584 }
585
586 ?>