]> git.donarmstrong.com Git - roundcube.git/blob - program/lib/Mail/mime.php
Imported Upstream version 0.1~beta2.2~dfsg
[roundcube.git] / program / lib / Mail / mime.php
1 <?php
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                                |
6 // | All rights reserved.                                                  |
7 // |                                                                       |
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: mime.php 260 2006-06-09 16:47:21Z afladmark $
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  *
54  * @author   Richard Heyes <richard.heyes@heyes-computing.net>
55  * @author   Tomas V.V.Cox <cox@idecnet.com>
56  * @package  Mail
57  * @access   public
58  */
59 class Mail_mime
60 {
61     /**
62      * Contains the plain text part of the email
63      * @var string
64      */
65     var $_txtbody;
66     /**
67      * Contains the html part of the email
68      * @var string
69      */
70     var $_htmlbody;
71     /**
72      * contains the mime encoded text
73      * @var string
74      */
75     var $_mime;
76     /**
77      * contains the multipart content
78      * @var string
79      */
80     var $_multipart;
81     /**
82      * list of the attached images
83      * @var array
84      */
85     var $_html_images = array();
86     /**
87      * list of the attachements
88      * @var array
89      */
90     var $_parts = array();
91     /**
92      * Build parameters
93      * @var array
94      */
95     var $_build_params = array();
96     /**
97      * Headers for the mail
98      * @var array
99      */
100     var $_headers = array();
101     /**
102      * End Of Line sequence (for serialize)
103      * @var string
104      */
105     var $_eol;
106
107
108     /**
109      * Constructor function
110      *
111      * @access public
112      */
113     function Mail_mime($crlf = "\r\n")
114     {
115         $this->_setEOL($crlf);
116         $this->_build_params = array(
117                                      'head_encoding' => 'quoted-printable',
118                                      'text_encoding' => '7bit',
119                                      'html_encoding' => 'quoted-printable',
120                                      '7bit_wrap'     => 998,
121                                      'html_charset'  => 'ISO-8859-1',
122                                      'text_charset'  => 'ISO-8859-1',
123                                      'head_charset'  => 'ISO-8859-1'
124                                     );
125     }
126
127     /**
128      * Wakeup (unserialize) - re-sets EOL constant
129      *
130      * @access private
131      */
132     function __wakeup()
133     {
134         $this->_setEOL($this->_eol);
135     }
136
137     /**
138      * Accessor function to set the body text. Body text is used if
139      * it's not an html mail being sent or else is used to fill the
140      * text/plain part that emails clients who don't support
141      * html should show.
142      *
143      * @param  string  $data   Either a string or
144      *                         the file name with the contents
145      * @param  bool    $isfile If true the first param should be treated
146      *                         as a file name, else as a string (default)
147      * @param  bool    $append If true the text or file is appended to
148      *                         the existing body, else the old body is
149      *                         overwritten
150      * @return mixed   true on success or PEAR_Error object
151      * @access public
152      */
153     function setTXTBody($data, $isfile = false, $append = false)
154     {
155         if (!$isfile) {
156             if (!$append) {
157                 $this->_txtbody = $data;
158             } else {
159                 $this->_txtbody .= $data;
160             }
161         } else {
162             $cont = $this->_file2str($data);
163             if (PEAR::isError($cont)) {
164                 return $cont;
165             }
166             if (!$append) {
167                 $this->_txtbody = $cont;
168             } else {
169                 $this->_txtbody .= $cont;
170             }
171         }
172         return true;
173     }
174
175     /**
176      * Adds a html part to the mail
177      *
178      * @param  string  $data   Either a string or the file name with the
179      *                         contents
180      * @param  bool    $isfile If true the first param should be treated
181      *                         as a file name, else as a string (default)
182      * @return mixed   true on success or PEAR_Error object
183      * @access public
184      */
185     function setHTMLBody($data, $isfile = false)
186     {
187         if (!$isfile) {
188             $this->_htmlbody = $data;
189         } else {
190             $cont = $this->_file2str($data);
191             if (PEAR::isError($cont)) {
192                 return $cont;
193             }
194             $this->_htmlbody = $cont;
195         }
196
197         return true;
198     }
199
200     /**
201      * Adds an image to the list of embedded images.
202      *
203      * @param  string  $file       The image file name OR image data itself
204      * @param  string  $c_type     The content type
205      * @param  string  $name       The filename of the image.
206      *                             Only use if $file is the image data
207      * @param  bool    $isfilename Whether $file is a filename or not
208      *                             Defaults to true
209      * @return mixed   true on success or PEAR_Error object
210      * @access public
211      */
212     function addHTMLImage($file, $c_type='application/octet-stream',
213                           $name = '', $isfilename = true)
214     {
215         $filedata = ($isfilename === true) ? $this->_file2str($file)
216                                            : $file;
217         if ($isfilename === true) {
218             $filename = ($name == '' ? $file : $name);
219         } else {
220             $filename = $name;
221         }
222         if (PEAR::isError($filedata)) {
223             return $filedata;
224         }
225         $this->_html_images[] = array(
226                                       'body'   => $filedata,
227                                       'name'   => $filename,
228                                       'c_type' => $c_type,
229                                       'cid'    => md5(uniqid(time()))
230                                      );
231         return true;
232     }
233
234     /**
235      * Adds a file to the list of attachments.
236      *
237      * @param  string  $file        The file name of the file to attach
238      *                              OR the file contents itself
239      * @param  string  $c_type      The content type
240      * @param  string  $name        The filename of the attachment
241      *                              Only use if $file is the contents
242      * @param  bool    $isFilename  Whether $file is a filename or not
243      *                              Defaults to true
244      * @param  string  $encoding    The type of encoding to use.
245      *                              Defaults to base64.
246      *                              Possible values: 7bit, 8bit, base64, 
247      *                              or quoted-printable.
248      * @param  string  $disposition The content-disposition of this file
249      *                              Defaults to attachment.
250      *                              Possible values: attachment, inline.
251      * @param  string  $charset     The character set used in the filename
252      *                              of this attachment.
253      * @return mixed true on success or PEAR_Error object
254      * @access public
255      */
256     function addAttachment($file, $c_type = 'application/octet-stream',
257                            $name = '', $isfilename = true,
258                            $encoding = 'base64',
259                            $disposition = 'attachment', $charset = '')
260     {
261         $filedata = ($isfilename === true) ? $this->_file2str($file)
262                                            : $file;
263         if ($isfilename === true) {
264             // Force the name the user supplied, otherwise use $file
265             $filename = (!empty($name)) ? $name : $file;
266         } else {
267             $filename = $name;
268         }
269         if (empty($filename)) {
270             $err = PEAR::raiseError(
271               "The supplied filename for the attachment can't be empty"
272             );
273             return $err;
274         }
275         $filename = basename($filename);
276         if (PEAR::isError($filedata)) {
277             return $filedata;
278         }
279
280         $this->_parts[] = array(
281                                 'body'        => $filedata,
282                                 'name'        => $filename,
283                                 'c_type'      => $c_type,
284                                 'encoding'    => $encoding,
285                                 'charset'     => $charset,
286                                 'disposition' => $disposition
287                                );
288         return true;
289     }
290
291     /**
292      * Get the contents of the given file name as string
293      *
294      * @param  string  $file_name  path of file to process
295      * @return string  contents of $file_name
296      * @access private
297      */
298     function &_file2str($file_name)
299     {
300         if (!is_readable($file_name)) {
301             $err = PEAR::raiseError('File is not readable ' . $file_name);
302             return $err;
303         }
304         if (!$fd = fopen($file_name, 'rb')) {
305             $err = PEAR::raiseError('Could not open ' . $file_name);
306             return $err;
307         }
308         $filesize = filesize($file_name);
309         if ($filesize == 0){
310             $cont =  "";
311         }else{
312             if ($magic_quote_setting = get_magic_quotes_runtime()){
313                 set_magic_quotes_runtime(0);
314             }
315             $cont = fread($fd, $filesize);
316             if ($magic_quote_setting){
317                 set_magic_quotes_runtime($magic_quote_setting);
318             }
319         }
320         fclose($fd);
321         return $cont;
322     }
323
324     /**
325      * Adds a text subpart to the mimePart object and
326      * returns it during the build process.
327      *
328      * @param mixed    The object to add the part to, or
329      *                 null if a new object is to be created.
330      * @param string   The text to add.
331      * @return object  The text mimePart object
332      * @access private
333      */
334     function &_addTextPart(&$obj, $text)
335     {
336         $params['content_type'] = 'text/plain';
337         $params['encoding']     = $this->_build_params['text_encoding'];
338         $params['charset']      = $this->_build_params['text_charset'];
339         if (is_object($obj)) {
340             $ret = $obj->addSubpart($text, $params);
341             return $ret;
342         } else {
343             $ret = new Mail_mimePart($text, $params);
344             return $ret;
345         }
346     }
347
348     /**
349      * Adds a html subpart to the mimePart object and
350      * returns it during the build process.
351      *
352      * @param  mixed   The object to add the part to, or
353      *                 null if a new object is to be created.
354      * @return object  The html mimePart object
355      * @access private
356      */
357     function &_addHtmlPart(&$obj)
358     {
359         $params['content_type'] = 'text/html';
360         $params['encoding']     = $this->_build_params['html_encoding'];
361         $params['charset']      = $this->_build_params['html_charset'];
362         if (is_object($obj)) {
363             $ret = $obj->addSubpart($this->_htmlbody, $params);
364             return $ret;
365         } else {
366             $ret = new Mail_mimePart($this->_htmlbody, $params);
367             return $ret;
368         }
369     }
370
371     /**
372      * Creates a new mimePart object, using multipart/mixed as
373      * the initial content-type and returns it during the
374      * build process.
375      *
376      * @return object  The multipart/mixed mimePart object
377      * @access private
378      */
379     function &_addMixedPart()
380     {
381         $params['content_type'] = 'multipart/mixed';
382         $ret = new Mail_mimePart('', $params);
383         return $ret;
384     }
385
386     /**
387      * Adds a multipart/alternative part to a mimePart
388      * object (or creates one), and returns it during
389      * the build process.
390      *
391      * @param  mixed   The object to add the part to, or
392      *                 null if a new object is to be created.
393      * @return object  The multipart/mixed mimePart object
394      * @access private
395      */
396     function &_addAlternativePart(&$obj)
397     {
398         $params['content_type'] = 'multipart/alternative';
399         if (is_object($obj)) {
400             return $obj->addSubpart('', $params);
401         } else {
402             $ret = new Mail_mimePart('', $params);
403             return $ret;
404         }
405     }
406
407     /**
408      * Adds a multipart/related part to a mimePart
409      * object (or creates one), and returns it during
410      * the build process.
411      *
412      * @param mixed    The object to add the part to, or
413      *                 null if a new object is to be created
414      * @return object  The multipart/mixed mimePart object
415      * @access private
416      */
417     function &_addRelatedPart(&$obj)
418     {
419         $params['content_type'] = 'multipart/related';
420         if (is_object($obj)) {
421             return $obj->addSubpart('', $params);
422         } else {
423             $ret = new Mail_mimePart('', $params);
424             return $ret;
425         }
426     }
427
428     /**
429      * Adds an html image subpart to a mimePart object
430      * and returns it during the build process.
431      *
432      * @param  object  The mimePart to add the image to
433      * @param  array   The image information
434      * @return object  The image mimePart object
435      * @access private
436      */
437     function &_addHtmlImagePart(&$obj, $value)
438     {
439         $params['content_type'] = $value['c_type'] . '; ' .
440                                   'name="' . $value['name'] . '"';
441         $params['encoding']     = 'base64';
442         $params['disposition']  = 'inline';
443         $params['dfilename']    = $value['name'];
444         $params['cid']          = $value['cid'];
445         $ret = $obj->addSubpart($value['body'], $params);
446         return $ret;
447         
448     }
449
450     /**
451      * Adds an attachment subpart to a mimePart object
452      * and returns it during the build process.
453      *
454      * @param  object  The mimePart to add the image to
455      * @param  array   The attachment information
456      * @return object  The image mimePart object
457      * @access private
458      */
459     function &_addAttachmentPart(&$obj, $value)
460     {
461         $params['dfilename']    = $value['name'];
462         $params['encoding']     = $value['encoding'];
463         if ($value['disposition'] != "inline") {
464             $fname = array("fname" => $value['name']);
465             $fname_enc = $this->_encodeHeaders($fname);
466             $params['dfilename'] = $fname_enc['fname'];
467         }
468         if ($value['charset']) {
469             $params['charset'] = $value['charset'];
470         }
471         $params['content_type'] = $value['c_type'] . '; ' .
472                                   'name="' . $params['dfilename'] . '"';
473         $params['disposition']  = isset($value['disposition']) ? 
474                                   $value['disposition'] : 'attachment';
475         $ret = $obj->addSubpart($value['body'], $params);
476         return $ret;
477     }
478
479     /**
480      * Returns the complete e-mail, ready to send using an alternative
481      * mail delivery method. Note that only the mailpart that is made
482      * with Mail_Mime is created. This means that,
483      * YOU WILL HAVE NO TO: HEADERS UNLESS YOU SET IT YOURSELF 
484      * using the $xtra_headers parameter!
485      * 
486      * @param  string $separation   The separation etween these two parts.
487      * @param  array  $build_params The Build parameters passed to the
488      *                              &get() function. See &get for more info.
489      * @param  array  $xtra_headers The extra headers that should be passed
490      *                              to the &headers() function.
491      *                              See that function for more info.
492      * @param  bool   $overwrite    Overwrite the existing headers with new.
493      * @return string The complete e-mail.
494      * @access public
495      */
496     function getMessage($separation = null, $build_params = null, $xtra_headers = null, $overwrite = false)
497     {
498         if ($separation === null)
499         {
500             $separation = MAIL_MIME_CRLF;
501         }
502         $body = $this->get($build_params);
503         $head = $this->txtHeaders($xtra_headers, $overwrite);
504         $mail = $head . $separation . $body;
505         return $mail;
506     }
507
508
509     /**
510      * Builds the multipart message from the list ($this->_parts) and
511      * returns the mime content.
512      *
513      * @param  array  Build parameters that change the way the email
514      *                is built. Should be associative. Can contain:
515      *                head_encoding  -  What encoding to use for the headers. 
516      *                                  Options: quoted-printable or base64
517      *                                  Default is quoted-printable
518      *                text_encoding  -  What encoding to use for plain text
519      *                                  Options: 7bit, 8bit, base64, or quoted-printable
520      *                                  Default is 7bit
521      *                html_encoding  -  What encoding to use for html
522      *                                  Options: 7bit, 8bit, base64, or quoted-printable
523      *                                  Default is quoted-printable
524      *                7bit_wrap      -  Number of characters before text is
525      *                                  wrapped in 7bit encoding
526      *                                  Default is 998
527      *                html_charset   -  The character set to use for html.
528      *                                  Default is iso-8859-1
529      *                text_charset   -  The character set to use for text.
530      *                                  Default is iso-8859-1
531      *                head_charset   -  The character set to use for headers.
532      *                                  Default is iso-8859-1
533      * @return string The mime content
534      * @access public
535      */
536     function &get($build_params = null)
537     {
538         if (isset($build_params)) {
539             while (list($key, $value) = each($build_params)) {
540                 $this->_build_params[$key] = $value;
541             }
542         }
543
544         if (!empty($this->_html_images) AND isset($this->_htmlbody)) {
545             foreach ($this->_html_images as $key => $value) {
546                 $regex = array();
547                 $regex[] = '#(\s)((?i)src|background|href(?-i))\s*=\s*(["\']?)' .
548                             preg_quote($value['name'], '#') . '\3#';
549                 $regex[] = '#(?i)url(?-i)\(\s*(["\']?)' .
550                             preg_quote($value['name'], '#') . '\1\s*\)#';
551                 $rep = array();
552                 $rep[] = '\1\2=\3cid:' . $value['cid'] .'\3';
553                 $rep[] = 'url(\1cid:' . $value['cid'] . '\2)';
554                 $this->_htmlbody = preg_replace($regex, $rep,
555                                        $this->_htmlbody
556                                    );
557                 $this->_html_images[$key]['name'] = basename($this->_html_images[$key]['name']);
558             }
559         }
560
561         $null        = null;
562         $attachments = !empty($this->_parts)                ? true : false;
563         $html_images = !empty($this->_html_images)          ? true : false;
564         $html        = !empty($this->_htmlbody)             ? true : false;
565         $text        = (!$html AND !empty($this->_txtbody)) ? true : false;
566
567         switch (true) {
568         case $text AND !$attachments:
569             $message =& $this->_addTextPart($null, $this->_txtbody);
570             break;
571
572         case !$text AND !$html AND $attachments:
573             $message =& $this->_addMixedPart();
574             for ($i = 0; $i < count($this->_parts); $i++) {
575                 $this->_addAttachmentPart($message, $this->_parts[$i]);
576             }
577             break;
578
579         case $text AND $attachments:
580             $message =& $this->_addMixedPart();
581             $this->_addTextPart($message, $this->_txtbody);
582             for ($i = 0; $i < count($this->_parts); $i++) {
583                 $this->_addAttachmentPart($message, $this->_parts[$i]);
584             }
585             break;
586
587         case $html AND !$attachments AND !$html_images:
588             if (isset($this->_txtbody)) {
589                 $message =& $this->_addAlternativePart($null);
590                 $this->_addTextPart($message, $this->_txtbody);
591                 $this->_addHtmlPart($message);
592             } else {
593                 $message =& $this->_addHtmlPart($null);
594             }
595             break;
596
597         case $html AND !$attachments AND $html_images:
598             if (isset($this->_txtbody)) {
599                 $message =& $this->_addAlternativePart($null);
600                 $this->_addTextPart($message, $this->_txtbody);
601                 $related =& $this->_addRelatedPart($message);
602             } else {
603                 $message =& $this->_addRelatedPart($null);
604                 $related =& $message;
605             }
606             $this->_addHtmlPart($related);
607             for ($i = 0; $i < count($this->_html_images); $i++) {
608                 $this->_addHtmlImagePart($related, $this->_html_images[$i]);
609             }
610             break;
611
612         case $html AND $attachments AND !$html_images:
613             $message =& $this->_addMixedPart();
614             if (isset($this->_txtbody)) {
615                 $alt =& $this->_addAlternativePart($message);
616                 $this->_addTextPart($alt, $this->_txtbody);
617                 $this->_addHtmlPart($alt);
618             } else {
619                 $this->_addHtmlPart($message);
620             }
621             for ($i = 0; $i < count($this->_parts); $i++) {
622                 $this->_addAttachmentPart($message, $this->_parts[$i]);
623             }
624             break;
625
626         case $html AND $attachments AND $html_images:
627             $message =& $this->_addMixedPart();
628             if (isset($this->_txtbody)) {
629                 $alt =& $this->_addAlternativePart($message);
630                 $this->_addTextPart($alt, $this->_txtbody);
631                 $rel =& $this->_addRelatedPart($alt);
632             } else {
633                 $rel =& $this->_addRelatedPart($message);
634             }
635             $this->_addHtmlPart($rel);
636             for ($i = 0; $i < count($this->_html_images); $i++) {
637                 $this->_addHtmlImagePart($rel, $this->_html_images[$i]);
638             }
639             for ($i = 0; $i < count($this->_parts); $i++) {
640                 $this->_addAttachmentPart($message, $this->_parts[$i]);
641             }
642             break;
643
644         }
645
646         if (isset($message)) {
647             $output = $message->encode();
648             $this->_headers = array_merge($this->_headers,
649                                           $output['headers']);
650             $body = $output['body'];
651             return $body;
652
653         } else {
654             $ret = false;
655             return $ret;
656         }
657     }
658
659     /**
660      * Returns an array with the headers needed to prepend to the email
661      * (MIME-Version and Content-Type). Format of argument is:
662      * $array['header-name'] = 'header-value';
663      *
664      * @param  array $xtra_headers Assoc array with any extra headers.
665      *                             Optional.
666      * @param  bool  $overwrite    Overwrite already existing headers.
667      * @return array Assoc array with the mime headers
668      * @access public
669      */
670     function &headers($xtra_headers = null, $overwrite = false)
671     {
672         // Content-Type header should already be present,
673         // So just add mime version header
674         $headers['MIME-Version'] = '1.0';
675         if (isset($xtra_headers)) {
676             $headers = array_merge($headers, $xtra_headers);
677         }
678         if ($overwrite){
679             $this->_headers = array_merge($this->_headers, $headers);
680         }else{
681             $this->_headers = array_merge($headers, $this->_headers);
682         }
683
684         $encodedHeaders = $this->_encodeHeaders($this->_headers);
685         return $encodedHeaders;
686     }
687
688     /**
689      * Get the text version of the headers
690      * (usefull if you want to use the PHP mail() function)
691      *
692      * @param  array   $xtra_headers Assoc array with any extra headers.
693      *                               Optional.
694      * @param  bool    $overwrite    Overwrite the existing heaers with new.
695      * @return string  Plain text headers
696      * @access public
697      */
698     function txtHeaders($xtra_headers = null, $overwrite = false)
699     {
700         $headers = $this->headers($xtra_headers, $overwrite);
701         $ret = '';
702         foreach ($headers as $key => $val) {
703             $ret .= "$key: $val" . MAIL_MIME_CRLF;
704         }
705         return $ret;
706     }
707
708     /**
709      * Sets the Subject header
710      *
711      * @param  string $subject String to set the subject to
712      * access  public
713      */
714     function setSubject($subject)
715     {
716         $this->_headers['Subject'] = $subject;
717     }
718
719     /**
720      * Set an email to the From (the sender) header
721      *
722      * @param  string $email The email direction to add
723      * @access public
724      */
725     function setFrom($email)
726     {
727         $this->_headers['From'] = $email;
728     }
729
730     /**
731      * Add an email to the Cc (carbon copy) header
732      * (multiple calls to this method are allowed)
733      *
734      * @param  string $email The email direction to add
735      * @access public
736      */
737     function addCc($email)
738     {
739         if (isset($this->_headers['Cc'])) {
740             $this->_headers['Cc'] .= ", $email";
741         } else {
742             $this->_headers['Cc'] = $email;
743         }
744     }
745
746     /**
747      * Add an email to the Bcc (blank carbon copy) header
748      * (multiple calls to this method are allowed)
749      *
750      * @param  string $email The email direction to add
751      * @access public
752      */
753     function addBcc($email)
754     {
755         if (isset($this->_headers['Bcc'])) {
756             $this->_headers['Bcc'] .= ", $email";
757         } else {
758             $this->_headers['Bcc'] = $email;
759         }
760     }
761
762     /**
763      * Since the PHP send function requires you to specifiy 
764      * recipients (To: header) separately from the other
765      * headers, the To: header is not properly encoded.
766      * To fix this, you can use this public method to 
767      * encode your recipients before sending to the send
768      * function
769      *
770      * @param  string $recipients A comma-delimited list of recipients
771      * @return string Encoded data
772      * @access public
773      */
774     function encodeRecipients($recipients)
775     {
776         $input = array("To" => $recipients);
777         $retval = $this->_encodeHeaders($input);
778         return $retval["To"] ;
779     }
780
781     /**
782      * Encodes a header as per RFC2047
783      *
784      * @param  array $input The header data to encode
785      * @return array Encoded data
786      * @access private
787      */
788     function _encodeHeaders($input)
789     {
790         foreach ($input as $hdr_name => $hdr_value) {
791             if (function_exists('iconv_mime_encode') && preg_match('#[\x80-\xFF]{1}#', $hdr_value)){
792                 $imePref = array();
793                 if ($this->_build_params['head_encoding'] == 'base64'){
794                     $imePrefs['scheme'] = 'B';
795                 }else{
796                     $imePrefs['scheme'] = 'Q';
797                 }
798                 $imePrefs['input-charset']  = $this->_build_params['head_charset'];
799                 $imePrefs['output-charset'] = $this->_build_params['head_charset'];
800                 $hdr_value = iconv_mime_encode($hdr_name, $hdr_value, $imePrefs);
801                 $hdr_value = preg_replace("#^{$hdr_name}\:\ #", "", $hdr_value);
802             }elseif (preg_match('#[\x80-\xFF]{1}#', $hdr_value)){
803                 //This header contains non ASCII chars and should be encoded.
804                 switch ($this->_build_params['head_encoding']) {
805                 case 'base64':
806                     //Base64 encoding has been selected.
807                     
808                     //Generate the header using the specified params and dynamicly 
809                     //determine the maximum length of such strings.
810                     //75 is the value specified in the RFC. The -2 is there so 
811                     //the later regexp doesn't break any of the translated chars.
812                     $prefix = '=?' . $this->_build_params['head_charset'] . '?B?';
813                     $suffix = '?=';
814                     $maxLength = 75 - strlen($prefix . $suffix) - 2;
815                     $maxLength1stLine = $maxLength - strlen($hdr_name);
816                     
817                     //Base64 encode the entire string
818                     $hdr_value = base64_encode($hdr_value);
819
820                     //This regexp will break base64-encoded text at every 
821                     //$maxLength but will not break any encoded letters.
822                     $reg1st = "|.{0,$maxLength1stLine}[^\=][^\=]|";
823                     $reg2nd = "|.{0,$maxLength}[^\=][^\=]|";
824                     break;
825                 case 'quoted-printable':
826                 default:
827                     //quoted-printable encoding has been selected
828                     
829                     //Generate the header using the specified params and dynamicly 
830                     //determine the maximum length of such strings.
831                     //75 is the value specified in the RFC. The -2 is there so 
832                     //the later regexp doesn't break any of the translated chars.
833                     $prefix = '=?' . $this->_build_params['head_charset'] . '?Q?';
834                     $suffix = '?=';
835                     $maxLength = 75 - strlen($prefix . $suffix) - 2;
836                     $maxLength1stLine = $maxLength - strlen($hdr_name);
837                     
838                     //Replace all special characters used by the encoder.
839                     $search  = array("=",   "_",   "?",   " ");
840                     $replace = array("=3D", "=5F", "=3F", "_");
841                     $hdr_value = str_replace($search, $replace, $hdr_value);
842                     
843                     //Replace all extended characters (\x80-xFF) with their
844                     //ASCII values.
845                     $hdr_value = preg_replace(
846                         '#([\x80-\xFF])#e',
847                         '"=" . strtoupper(dechex(ord("\1")))',
848                         $hdr_value
849                     );
850                     //This regexp will break QP-encoded text at every $maxLength
851                     //but will not break any encoded letters.
852                     $reg1st = "|(.{0,$maxLength})[^\=]|";
853                     $reg2nd = "|(.{0,$maxLength})[^\=]|";
854                     break;
855                 }
856                 //Begin with the regexp for the first line.
857                 $reg = $reg1st;
858                 $output = "";
859                 while ($hdr_value) {
860                     //Split translated string at every $maxLength
861                     //But make sure not to break any translated chars.
862                     $found = preg_match($reg, $hdr_value, $matches);
863                     
864                     //After this first line, we need to use a different
865                     //regexp for the first line.
866                     $reg = $reg2nd;
867
868                     //Save the found part and encapsulate it in the
869                     //prefix & suffix. Then remove the part from the
870                     //$hdr_value variable.
871                     if ($found){
872                         $part = $matches[0];
873                         $hdr_value = substr($hdr_value, strlen($matches[0]));
874                     }else{
875                         $part = $hdr_value;
876                         $hdr_value = "";
877                     }
878                     
879                     //RFC 2047 specifies that any split header should be seperated
880                     //by a CRLF SPACE. 
881                     if ($output){
882                         $output .=  "\r\n ";
883                     }
884                     $output .= $prefix . $part . $suffix;
885                 }
886                 $hdr_value = $output;
887             }
888             $input[$hdr_name] = $hdr_value;
889         }
890
891         return $input;
892     }
893
894     /**
895      * Set the object's end-of-line and define the constant if applicable
896      *
897      * @param string $eol End Of Line sequence
898      * @access private
899      */
900     function _setEOL($eol)
901     {
902         $this->_eol = $eol;
903         if (!defined('MAIL_MIME_CRLF')) {
904             define('MAIL_MIME_CRLF', $this->_eol, true);
905         }
906     }
907
908     
909
910 } // End of class
911 ?>
912