thomascube
2005-11-18 fbf77b4493f1b77c99751d8a86365c712ae3fb1b
commit | author | age
4e17e6 1 <?php
T 2 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
3 // +-----------------------------------------------------------------------+
4 // | Copyright (c) 2002-2003  Richard Heyes                                |
5 // | Copyright (c) 2003-2005  The PHP Group                                |
15fee7 6 // | All rights reserved.                                                  |
4e17e6 7 // |                                                                       |
T 8 // | Redistribution and use in source and binary forms, with or without    |
9 // | modification, are permitted provided that the following conditions    |
10 // | are met:                                                              |
11 // |                                                                       |
12 // | o Redistributions of source code must retain the above copyright      |
13 // |   notice, this list of conditions and the following disclaimer.       |
14 // | o Redistributions in binary form must reproduce the above copyright   |
15 // |   notice, this list of conditions and the following disclaimer in the |
16 // |   documentation and/or other materials provided with the distribution.|
17 // | o The names of the authors may not be used to endorse or promote      |
18 // |   products derived from this software without specific prior written  |
19 // |   permission.                                                         |
20 // |                                                                       |
21 // | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS   |
22 // | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT     |
23 // | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
24 // | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT  |
25 // | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
26 // | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT      |
27 // | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
28 // | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
29 // | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT   |
30 // | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
31 // | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.  |
32 // |                                                                       |
33 // +-----------------------------------------------------------------------+
34 // | Author: Richard Heyes <richard@phpguru.org>                           |
35 // |         Tomas V.V.Cox <cox@idecnet.com> (port to PEAR)                |
36 // +-----------------------------------------------------------------------+
37 //
38 // $Id$
39
40 require_once('PEAR.php');
41 require_once('Mail/mimePart.php');
42
43 /**
44  * Mime mail composer class. Can handle: text and html bodies, embedded html
45  * images and attachments.
46  * Documentation and examples of this class are avaible here:
47  * http://pear.php.net/manual/
48  *
49  * @notes This class is based on HTML Mime Mail class from
50  *   Richard Heyes <richard@phpguru.org> which was based also
51  *   in the mime_mail.class by Tobias Ratschiller <tobias@dnet.it> and
52  *   Sascha Schumann <sascha@schumann.cx>
53  *
15fee7 54  * @notes Replaced method _encodeHeaders by the version of ed@avi.ru
T 55  *        See http://pear.php.net/bugs/bug.php?id=30 for details
4e17e6 56  *
T 57  * @author   Richard Heyes <richard.heyes@heyes-computing.net>
58  * @author   Tomas V.V.Cox <cox@idecnet.com>
59  * @package  Mail
60  * @access   public
61  */
62 class Mail_mime
63 {
64     /**
65      * Contains the plain text part of the email
66      * @var string
67      */
68     var $_txtbody;
69     /**
70      * Contains the html part of the email
71      * @var string
72      */
73     var $_htmlbody;
74     /**
75      * contains the mime encoded text
76      * @var string
77      */
78     var $_mime;
79     /**
80      * contains the multipart content
81      * @var string
82      */
83     var $_multipart;
84     /**
85      * list of the attached images
86      * @var array
87      */
88     var $_html_images = array();
89     /**
90      * list of the attachements
91      * @var array
92      */
93     var $_parts = array();
94     /**
95      * Build parameters
96      * @var array
97      */
98     var $_build_params = array();
99     /**
100      * Headers for the mail
101      * @var array
102      */
103     var $_headers = array();
104     /**
105      * End Of Line sequence (for serialize)
106      * @var string
107      */
108     var $_eol;
109
110
111     /**
112      * Constructor function
113      *
114      * @access public
115      */
116     function Mail_mime($crlf = "\r\n")
117     {
118         $this->_setEOL($crlf);
119         $this->_build_params = array(
120                                      'text_encoding' => '7bit',
121                                      'html_encoding' => 'quoted-printable',
968bdc 122                                      'head_encoding' => 'quoted-printable',
4e17e6 123                                      '7bit_wrap'     => 998,
T 124                                      'html_charset'  => 'ISO-8859-1',
125                                      'text_charset'  => 'ISO-8859-1',
126                                      'head_charset'  => 'ISO-8859-1'
127                                     );
128     }
129
130     /**
131      * Wakeup (unserialize) - re-sets EOL constant
132      *
133      * @access private
134      */
135     function __wakeup()
136     {
137         $this->_setEOL($this->_eol);
138     }
139
140     /**
141      * Accessor function to set the body text. Body text is used if
142      * it's not an html mail being sent or else is used to fill the
143      * text/plain part that emails clients who don't support
144      * html should show.
145      *
146      * @param  string  $data   Either a string or
147      *                         the file name with the contents
148      * @param  bool    $isfile If true the first param should be treated
149      *                         as a file name, else as a string (default)
150      * @param  bool    $append If true the text or file is appended to
151      *                         the existing body, else the old body is
152      *                         overwritten
153      * @return mixed   true on success or PEAR_Error object
154      * @access public
155      */
156     function setTXTBody($data, $isfile = false, $append = false)
157     {
158         if (!$isfile) {
159             if (!$append) {
160                 $this->_txtbody = $data;
161             } else {
162                 $this->_txtbody .= $data;
163             }
164         } else {
165             $cont = $this->_file2str($data);
166             if (PEAR::isError($cont)) {
167                 return $cont;
168             }
169             if (!$append) {
170                 $this->_txtbody = $cont;
171             } else {
172                 $this->_txtbody .= $cont;
173             }
174         }
175         return true;
176     }
177
178     /**
179      * Adds a html part to the mail
180      *
181      * @param  string  $data   Either a string or the file name with the
182      *                         contents
183      * @param  bool    $isfile If true the first param should be treated
184      *                         as a file name, else as a string (default)
185      * @return mixed   true on success or PEAR_Error object
186      * @access public
187      */
188     function setHTMLBody($data, $isfile = false)
189     {
190         if (!$isfile) {
191             $this->_htmlbody = $data;
192         } else {
193             $cont = $this->_file2str($data);
194             if (PEAR::isError($cont)) {
195                 return $cont;
196             }
197             $this->_htmlbody = $cont;
198         }
199
200         return true;
201     }
202
203     /**
204      * Adds an image to the list of embedded images.
205      *
206      * @param  string  $file       The image file name OR image data itself
207      * @param  string  $c_type     The content type
208      * @param  string  $name       The filename of the image.
209      *                             Only use if $file is the image data
210      * @param  bool    $isfilename Whether $file is a filename or not
211      *                             Defaults to true
212      * @return mixed   true on success or PEAR_Error object
213      * @access public
214      */
215     function addHTMLImage($file, $c_type='application/octet-stream',
216                           $name = '', $isfilename = true)
217     {
218         $filedata = ($isfilename === true) ? $this->_file2str($file)
219                                            : $file;
220         if ($isfilename === true) {
221             $filename = ($name == '' ? basename($file) : basename($name));
222         } else {
223             $filename = basename($name);
224         }
225         if (PEAR::isError($filedata)) {
226             return $filedata;
227         }
228         $this->_html_images[] = array(
229                                       'body'   => $filedata,
230                                       'name'   => $filename,
231                                       'c_type' => $c_type,
232                                       'cid'    => md5(uniqid(time()))
233                                      );
234         return true;
235     }
236
237     /**
238      * Adds a file to the list of attachments.
239      *
240      * @param  string  $file       The file name of the file to attach
241      *                             OR the file data itself
242      * @param  string  $c_type     The content type
243      * @param  string  $name       The filename of the attachment
244      *                             Only use if $file is the file data
245      * @param  bool    $isFilename Whether $file is a filename or not
246      *                             Defaults to true
247      * @return mixed true on success or PEAR_Error object
248      * @access public
249      */
250     function addAttachment($file, $c_type = 'application/octet-stream',
251                            $name = '', $isfilename = true,
252                            $encoding = 'base64')
253     {
254         $filedata = ($isfilename === true) ? $this->_file2str($file)
255                                            : $file;
256         if ($isfilename === true) {
257             // Force the name the user supplied, otherwise use $file
258             $filename = (!empty($name)) ? $name : $file;
259         } else {
260             $filename = $name;
261         }
262         if (empty($filename)) {
263             return PEAR::raiseError(
264               'The supplied filename for the attachment can\'t be empty'
265             );
266         }
267         $filename = basename($filename);
268         if (PEAR::isError($filedata)) {
269             return $filedata;
270         }
271
272         $this->_parts[] = array(
273                                 'body'     => $filedata,
274                                 'name'     => $filename,
275                                 'c_type'   => $c_type,
276                                 'encoding' => $encoding
277                                );
278         return true;
279     }
280
281     /**
282      * Get the contents of the given file name as string
283      *
284      * @param  string  $file_name  path of file to process
285      * @return string  contents of $file_name
286      * @access private
287      */
288     function &_file2str($file_name)
289     {
290         if (!is_readable($file_name)) {
291             return PEAR::raiseError('File is not readable ' . $file_name);
292         }
293         if (!$fd = fopen($file_name, 'rb')) {
294             return PEAR::raiseError('Could not open ' . $file_name);
295         }
15fee7 296         $filesize = filesize($file_name);
T 297         if ($filesize == 0){
298             $cont =  "";
299         }else{
300             $cont = fread($fd, $filesize);
301         }
4e17e6 302         fclose($fd);
T 303         return $cont;
304     }
305
306     /**
307      * Adds a text subpart to the mimePart object and
308      * returns it during the build process.
309      *
310      * @param mixed    The object to add the part to, or
311      *                 null if a new object is to be created.
312      * @param string   The text to add.
313      * @return object  The text mimePart object
314      * @access private
315      */
316     function &_addTextPart(&$obj, $text)
317     {
318         $params['content_type'] = 'text/plain';
319         $params['encoding']     = $this->_build_params['text_encoding'];
320         $params['charset']      = $this->_build_params['text_charset'];
321         if (is_object($obj)) {
322             return $obj->addSubpart($text, $params);
323         } else {
324             return new Mail_mimePart($text, $params);
325         }
326     }
327
328     /**
329      * Adds a html subpart to the mimePart object and
330      * returns it during the build process.
331      *
332      * @param  mixed   The object to add the part to, or
333      *                 null if a new object is to be created.
334      * @return object  The html mimePart object
335      * @access private
336      */
337     function &_addHtmlPart(&$obj)
338     {
339         $params['content_type'] = 'text/html';
340         $params['encoding']     = $this->_build_params['html_encoding'];
341         $params['charset']      = $this->_build_params['html_charset'];
342         if (is_object($obj)) {
343             return $obj->addSubpart($this->_htmlbody, $params);
344         } else {
345             return new Mail_mimePart($this->_htmlbody, $params);
346         }
347     }
348
349     /**
350      * Creates a new mimePart object, using multipart/mixed as
351      * the initial content-type and returns it during the
352      * build process.
353      *
354      * @return object  The multipart/mixed mimePart object
355      * @access private
356      */
357     function &_addMixedPart()
358     {
359         $params['content_type'] = 'multipart/mixed';
360         return new Mail_mimePart('', $params);
361     }
362
363     /**
364      * Adds a multipart/alternative part to a mimePart
365      * object (or creates one), and returns it during
366      * the build process.
367      *
368      * @param  mixed   The object to add the part to, or
369      *                 null if a new object is to be created.
370      * @return object  The multipart/mixed mimePart object
371      * @access private
372      */
373     function &_addAlternativePart(&$obj)
374     {
375         $params['content_type'] = 'multipart/alternative';
376         if (is_object($obj)) {
377             return $obj->addSubpart('', $params);
378         } else {
379             return new Mail_mimePart('', $params);
380         }
381     }
382
383     /**
384      * Adds a multipart/related part to a mimePart
385      * object (or creates one), and returns it during
386      * the build process.
387      *
388      * @param mixed    The object to add the part to, or
389      *                 null if a new object is to be created
390      * @return object  The multipart/mixed mimePart object
391      * @access private
392      */
393     function &_addRelatedPart(&$obj)
394     {
395         $params['content_type'] = 'multipart/related';
396         if (is_object($obj)) {
397             return $obj->addSubpart('', $params);
398         } else {
399             return new Mail_mimePart('', $params);
400         }
401     }
402
403     /**
404      * Adds an html image subpart to a mimePart object
405      * and returns it during the build process.
406      *
407      * @param  object  The mimePart to add the image to
408      * @param  array   The image information
409      * @return object  The image mimePart object
410      * @access private
411      */
412     function &_addHtmlImagePart(&$obj, $value)
413     {
414         $params['content_type'] = $value['c_type'];
415         $params['encoding']     = 'base64';
416         $params['disposition']  = 'inline';
417         $params['dfilename']    = $value['name'];
418         $params['cid']          = $value['cid'];
419         $obj->addSubpart($value['body'], $params);
420     }
421
422     /**
423      * Adds an attachment subpart to a mimePart object
424      * and returns it during the build process.
425      *
426      * @param  object  The mimePart to add the image to
427      * @param  array   The attachment information
428      * @return object  The image mimePart object
429      * @access private
430      */
431     function &_addAttachmentPart(&$obj, $value)
432     {
433         $params['content_type'] = $value['c_type'];
434         $params['encoding']     = $value['encoding'];
435         $params['disposition']  = 'attachment';
436         $params['dfilename']    = $value['name'];
437         $obj->addSubpart($value['body'], $params);
438     }
439
440     /**
441      * Builds the multipart message from the list ($this->_parts) and
442      * returns the mime content.
443      *
444      * @param  array  Build parameters that change the way the email
445      *                is built. Should be associative. Can contain:
446      *                text_encoding  -  What encoding to use for plain text
447      *                                  Default is 7bit
448      *                html_encoding  -  What encoding to use for html
449      *                                  Default is quoted-printable
450      *                7bit_wrap      -  Number of characters before text is
451      *                                  wrapped in 7bit encoding
452      *                                  Default is 998
453      *                html_charset   -  The character set to use for html.
454      *                                  Default is iso-8859-1
455      *                text_charset   -  The character set to use for text.
456      *                                  Default is iso-8859-1
457      *                head_charset   -  The character set to use for headers.
458      *                                  Default is iso-8859-1
459      * @return string The mime content
460      * @access public
461      */
462     function &get($build_params = null)
463     {
464         if (isset($build_params)) {
465             while (list($key, $value) = each($build_params)) {
466                 $this->_build_params[$key] = $value;
467             }
468         }
469
470         if (!empty($this->_html_images) AND isset($this->_htmlbody)) {
471             foreach ($this->_html_images as $value) {
15fee7 472                 $regex = '#(\s)((?i)src|background|href(?-i))\s*=\s*(["\']?)' . preg_quote($value['name'], '#') .
T 473                          '\3#';
474                 $rep = '\1\2=\3cid:' . $value['cid'] .'\3';
4e17e6 475                 $this->_htmlbody = preg_replace($regex, $rep,
T 476                                        $this->_htmlbody
477                                    );
478             }
479         }
480
481         $null        = null;
482         $attachments = !empty($this->_parts)                ? true : false;
483         $html_images = !empty($this->_html_images)          ? true : false;
484         $html        = !empty($this->_htmlbody)             ? true : false;
485         $text        = (!$html AND !empty($this->_txtbody)) ? true : false;
486
487         switch (true) {
488         case $text AND !$attachments:
489             $message =& $this->_addTextPart($null, $this->_txtbody);
490             break;
491
492         case !$text AND !$html AND $attachments:
493             $message =& $this->_addMixedPart();
494             for ($i = 0; $i < count($this->_parts); $i++) {
495                 $this->_addAttachmentPart($message, $this->_parts[$i]);
496             }
497             break;
498
499         case $text AND $attachments:
500             $message =& $this->_addMixedPart();
501             $this->_addTextPart($message, $this->_txtbody);
502             for ($i = 0; $i < count($this->_parts); $i++) {
503                 $this->_addAttachmentPart($message, $this->_parts[$i]);
504             }
505             break;
506
507         case $html AND !$attachments AND !$html_images:
508             if (isset($this->_txtbody)) {
509                 $message =& $this->_addAlternativePart($null);
510                 $this->_addTextPart($message, $this->_txtbody);
511                 $this->_addHtmlPart($message);
512             } else {
513                 $message =& $this->_addHtmlPart($null);
514             }
515             break;
516
517         case $html AND !$attachments AND $html_images:
518             if (isset($this->_txtbody)) {
519                 $message =& $this->_addAlternativePart($null);
520                 $this->_addTextPart($message, $this->_txtbody);
521                 $related =& $this->_addRelatedPart($message);
522             } else {
523                 $message =& $this->_addRelatedPart($null);
524                 $related =& $message;
525             }
526             $this->_addHtmlPart($related);
527             for ($i = 0; $i < count($this->_html_images); $i++) {
528                 $this->_addHtmlImagePart($related, $this->_html_images[$i]);
529             }
530             break;
531
532         case $html AND $attachments AND !$html_images:
533             $message =& $this->_addMixedPart();
534             if (isset($this->_txtbody)) {
535                 $alt =& $this->_addAlternativePart($message);
536                 $this->_addTextPart($alt, $this->_txtbody);
537                 $this->_addHtmlPart($alt);
538             } else {
539                 $this->_addHtmlPart($message);
540             }
541             for ($i = 0; $i < count($this->_parts); $i++) {
542                 $this->_addAttachmentPart($message, $this->_parts[$i]);
543             }
544             break;
545
546         case $html AND $attachments AND $html_images:
547             $message =& $this->_addMixedPart();
548             if (isset($this->_txtbody)) {
549                 $alt =& $this->_addAlternativePart($message);
550                 $this->_addTextPart($alt, $this->_txtbody);
551                 $rel =& $this->_addRelatedPart($alt);
552             } else {
553                 $rel =& $this->_addRelatedPart($message);
554             }
555             $this->_addHtmlPart($rel);
556             for ($i = 0; $i < count($this->_html_images); $i++) {
557                 $this->_addHtmlImagePart($rel, $this->_html_images[$i]);
558             }
559             for ($i = 0; $i < count($this->_parts); $i++) {
560                 $this->_addAttachmentPart($message, $this->_parts[$i]);
561             }
562             break;
563
564         }
565
566         if (isset($message)) {
567             $output = $message->encode();
568             $this->_headers = array_merge($this->_headers,
569                                           $output['headers']);
570             return $output['body'];
571
572         } else {
573             return false;
574         }
575     }
576
577     /**
578      * Returns an array with the headers needed to prepend to the email
579      * (MIME-Version and Content-Type). Format of argument is:
580      * $array['header-name'] = 'header-value';
581      *
582      * @param  array $xtra_headers Assoc array with any extra headers.
583      *                             Optional.
584      * @return array Assoc array with the mime headers
585      * @access public
586      */
587     function &headers($xtra_headers = null)
588     {
589         // Content-Type header should already be present,
590         // So just add mime version header
591         $headers['MIME-Version'] = '1.0';
592         if (isset($xtra_headers)) {
593             $headers = array_merge($headers, $xtra_headers);
594         }
595         $this->_headers = array_merge($headers, $this->_headers);
596
597         return $this->_encodeHeaders($this->_headers);
598     }
599
600     /**
601      * Get the text version of the headers
602      * (usefull if you want to use the PHP mail() function)
603      *
604      * @param  array   $xtra_headers Assoc array with any extra headers.
605      *                               Optional.
606      * @return string  Plain text headers
607      * @access public
608      */
609     function txtHeaders($xtra_headers = null)
610     {
611         $headers = $this->headers($xtra_headers);
612         $ret = '';
613         foreach ($headers as $key => $val) {
614             $ret .= "$key: $val" . MAIL_MIME_CRLF;
615         }
616         return $ret;
617     }
618
619     /**
620      * Sets the Subject header
621      *
622      * @param  string $subject String to set the subject to
623      * access  public
624      */
625     function setSubject($subject)
626     {
627         $this->_headers['Subject'] = $subject;
628     }
629
630     /**
631      * Set an email to the From (the sender) header
632      *
633      * @param  string $email The email direction to add
634      * @access public
635      */
636     function setFrom($email)
637     {
638         $this->_headers['From'] = $email;
639     }
640
641     /**
642      * Add an email to the Cc (carbon copy) header
643      * (multiple calls to this method are allowed)
644      *
645      * @param  string $email The email direction to add
646      * @access public
647      */
648     function addCc($email)
649     {
650         if (isset($this->_headers['Cc'])) {
651             $this->_headers['Cc'] .= ", $email";
652         } else {
653             $this->_headers['Cc'] = $email;
654         }
655     }
656
657     /**
658      * Add an email to the Bcc (blank carbon copy) header
659      * (multiple calls to this method are allowed)
660      *
661      * @param  string $email The email direction to add
662      * @access public
663      */
664     function addBcc($email)
665     {
666         if (isset($this->_headers['Bcc'])) {
667             $this->_headers['Bcc'] .= ", $email";
668         } else {
669             $this->_headers['Bcc'] = $email;
670         }
671     }
672
673     /**
15fee7 674     * Encodes a header as per RFC2047
T 675     *
676     * @param  string  $input The header data to encode
677     * @return string         Encoded data
678     * @access private
679     */
4e17e6 680     function _encodeHeaders($input)
T 681     {
682         foreach ($input as $hdr_name => $hdr_value) {
15fee7 683             preg_match_all('/([\w\-]*[\x80-\xFF]+[\w\-]*(\s+[\w\-]*[\x80-\xFF]+[\w\-]*)*)\s*/',
T 684             $hdr_value, $matches);
4e17e6 685             foreach ($matches[1] as $value) {
15fee7 686                 switch ($head_encoding = $this->_build_params['head_encoding']) {
T 687                 case 'base64':
688                     $symbol = 'B';
689                     $replacement = base64_encode($value);
690                     break;
691
692                 default:
693                     if ($head_encoding != 'quoted-printable') {
694                         PEAR::raiseError('Invalid header encoding specified; using `quoted-printable` instead',
695                                         NULL,
696                                         PEAR_ERROR_TRIGGER,
697                                         E_USER_WARNING);
698                     }
699
700                     $symbol = 'Q';
701                     $replacement = preg_replace('/([\s_=\?\x80-\xFF])/e', '"=" . strtoupper(dechex(ord("\1")))', $value);
702                 }
703                 $hdr_value = str_replace($value, '=?' . $this->_build_params['head_charset'] . '?' . $symbol . '?' . $replacement . '?=', $hdr_value);
4e17e6 704             }
T 705             $input[$hdr_name] = $hdr_value;
706         }
15fee7 707         
4e17e6 708         return $input;
T 709     }
710
711     /**
712      * Set the object's end-of-line and define the constant if applicable
713      *
714      * @param string $eol End Of Line sequence
715      * @access private
716      */
717     function _setEOL($eol)
718     {
719         $this->_eol = $eol;
720         if (!defined('MAIL_MIME_CRLF')) {
721             define('MAIL_MIME_CRLF', $this->_eol, true);
722         }
723     }
724
725     
726
727 } // End of class
728 ?>