]> git.donarmstrong.com Git - roundcube.git/blob - program/lib/Mail/mime.php
Imported Debian patch 0.1~rc1~dfsg-3
[roundcube.git] / program / lib / Mail / mime.php
1 <?php
2 /**
3  * The Mail_Mime class is used to create MIME E-mail messages
4  *
5  * The Mail_Mime class provides an OO interface to create MIME
6  * enabled email messages. This way you can create emails that
7  * contain plain-text bodies, HTML bodies, attachments, inline
8  * images and specific headers.
9  *
10  * Compatible with PHP versions 4 and 5
11  *
12  * LICENSE: This LICENSE is in the BSD license style.
13  * Copyright (c) 2002-2003, Richard Heyes <richard@phpguru.org>
14  * Copyright (c) 2003-2006, PEAR <pear-group@php.net>
15  * All rights reserved.
16  *
17  * Redistribution and use in source and binary forms, with or
18  * without modification, are permitted provided that the following
19  * conditions are met:
20  *
21  * - Redistributions of source code must retain the above copyright
22  *   notice, this list of conditions and the following disclaimer.
23  * - Redistributions in binary form must reproduce the above copyright
24  *   notice, this list of conditions and the following disclaimer in the
25  *   documentation and/or other materials provided with the distribution.
26  * - Neither the name of the authors, nor the names of its contributors 
27  *   may be used to endorse or promote products derived from this 
28  *   software without specific prior written permission.
29  *
30  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
31  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
32  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
33  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
34  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
35  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
36  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
37  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
38  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
39  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
40  * THE POSSIBILITY OF SUCH DAMAGE.
41  *
42  * @category   Mail
43  * @package    Mail_Mime
44  * @author     Richard Heyes  <richard@phpguru.org>
45  * @author     Tomas V.V. Cox <cox@idecnet.com>
46  * @author     Cipriano Groenendal <cipri@php.net>
47  * @author     Sean Coates <sean@php.net>
48  * @copyright  2003-2006 PEAR <pear-group@php.net>
49  * @license    http://www.opensource.org/licenses/bsd-license.php BSD License
50  * @version    CVS: $Id: mime.php 514 2007-03-21 09:54:10Z thomasb $
51  * @link       http://pear.php.net/package/Mail_mime
52  * @notes      This class is based on HTML Mime Mail class from
53  *             Richard Heyes <richard@phpguru.org> which was based also
54  *             in the mime_mail.class by Tobias Ratschiller <tobias@dnet.it>
55  *             and Sascha Schumann <sascha@schumann.cx>
56  */
57
58
59 /**
60  * require PEAR
61  *
62  * This package depends on PEAR to raise errors.
63  */
64 require_once('PEAR.php');
65
66 /**
67  * require Mail_mimePart
68  *
69  * Mail_mimePart contains the code required to
70  * create all the different parts a mail can
71  * consist of.
72  */
73 require_once('Mail/mimePart.php');
74
75
76 /**
77  * The Mail_Mime class provides an OO interface to create MIME
78  * enabled email messages. This way you can create emails that
79  * contain plain-text bodies, HTML bodies, attachments, inline
80  * images and specific headers.
81  *
82  * @category   Mail
83  * @package    Mail_Mime
84  * @author     Richard Heyes  <richard@phpguru.org>
85  * @author     Tomas V.V. Cox <cox@idecnet.com>
86  * @author     Cipriano Groenendal <cipri@php.net>
87  * @author     Sean Coates <sean@php.net>
88  * @copyright  2003-2006 PEAR <pear-group@php.net>
89  * @license    http://www.opensource.org/licenses/bsd-license.php BSD License
90  * @version    Release: @package_version@
91  * @link       http://pear.php.net/package/Mail_mime
92  */
93 class Mail_mime
94 {
95     /**
96      * Contains the plain text part of the email
97      *
98      * @var string
99      * @access private
100      */
101     var $_txtbody;
102
103     /**
104      * Contains the html part of the email
105      *
106      * @var string
107      * @access private
108      */
109     var $_htmlbody;
110
111     /**
112      * contains the mime encoded text
113      *
114      * @var string
115      * @access private
116      */
117     var $_mime;
118
119     /**
120      * contains the multipart content
121      *
122      * @var string
123      * @access private
124      */
125     var $_multipart;
126
127     /**
128      * list of the attached images
129      *
130      * @var array
131      * @access private
132      */
133     var $_html_images = array();
134
135     /**
136      * list of the attachements
137      *
138      * @var array
139      * @access private
140      */
141     var $_parts = array();
142
143     /**
144      * Build parameters
145      *
146      * @var array
147      * @access private
148      */
149     var $_build_params = array();
150
151     /**
152      * Headers for the mail
153      *
154      * @var array
155      * @access private
156      */
157     var $_headers = array();
158
159     /**
160      * End Of Line sequence (for serialize)
161      *
162      * @var string
163      * @access private
164      */
165     var $_eol;
166
167
168     /**
169      * Constructor function.
170      *
171      * @param string $crlf  what type of linebreak to use.
172      *                       Defaults to "\r\n"
173      * @return void
174      *
175      * @access public
176      */
177     function Mail_mime($crlf = "\r\n")
178     {
179         $this->_setEOL($crlf);
180         $this->_build_params = array(
181                                      'head_encoding' => 'quoted-printable',
182                                      'text_encoding' => '7bit',
183                                      'html_encoding' => 'quoted-printable',
184                                      '7bit_wrap'     => 998,
185                                      'html_charset'  => 'ISO-8859-1',
186                                      'text_charset'  => 'ISO-8859-1',
187                                      'head_charset'  => 'ISO-8859-1'
188                                     );
189     }
190
191     /**
192      * wakeup function called by unserialize. It re-sets the EOL constant
193      *
194      * @access private
195      */
196     function __wakeup()
197     {
198         $this->_setEOL($this->_eol);
199     }
200
201
202     /**
203      * Accessor function to set the body text. Body text is used if
204      * it's not an html mail being sent or else is used to fill the
205      * text/plain part that emails clients who don't support
206      * html should show.
207      *
208      * @param  string  $data   Either a string or
209      *                          the file name with the contents
210      * @param  bool    $isfile If true the first param should be treated
211      *                          as a file name, else as a string (default)
212      * @param  bool    $append If true the text or file is appended to
213      *                          the existing body, else the old body is
214      *                          overwritten
215      * @return mixed   true on success or PEAR_Error object
216      * @access public
217      */
218     function setTXTBody($data, $isfile = false, $append = false)
219     {
220         if (!$isfile) {
221             if (!$append) {
222                 $this->_txtbody = $data;
223             } else {
224                 $this->_txtbody .= $data;
225             }
226         } else {
227             $cont = $this->_file2str($data);
228             if (PEAR::isError($cont)) {
229                 return $cont;
230             }
231             if (!$append) {
232                 $this->_txtbody = $cont;
233             } else {
234                 $this->_txtbody .= $cont;
235             }
236         }
237         return true;
238     }
239
240     /**
241      * Adds a html part to the mail.
242      *
243      * @param  string  $data   either a string or the file name with the
244      *                          contents
245      * @param  bool    $isfile a flag that determines whether $data is a
246      *                          filename, or a string(false, default)
247      * @return bool    true on success
248      * @access public
249      */
250     function setHTMLBody($data, $isfile = false)
251     {
252         if (!$isfile) {
253             $this->_htmlbody = $data;
254         } else {
255             $cont = $this->_file2str($data);
256             if (PEAR::isError($cont)) {
257                 return $cont;
258             }
259             $this->_htmlbody = $cont;
260         }
261
262         return true;
263     }
264
265     /**
266      * Adds an image to the list of embedded images.
267      *
268      * @param  string  $file       the image file name OR image data itself
269      * @param  string  $c_type     the content type
270      * @param  string  $name       the filename of the image.
271      *                              Only use if $file is the image data.
272      * @param  bool    $isfile     whether $file is a filename or not.
273      *                              Defaults to true
274      * @return bool                true on success
275      * @access public
276      */
277     function addHTMLImage($file, $c_type='application/octet-stream',
278                           $name = '', $isfile = true)
279     {
280         $filedata = ($isfile === true) ? $this->_file2str($file)
281                                            : $file;
282         if ($isfile === true) {
283             $filename = ($name == '' ? $file : $name);
284         } else {
285             $filename = $name;
286         }
287         if (PEAR::isError($filedata)) {
288             return $filedata;
289         }
290         $this->_html_images[] = array(
291                                       'body'   => $filedata,
292                                       'name'   => $filename,
293                                       'c_type' => $c_type,
294                                       'cid'    => md5(uniqid(time()))
295                                      );
296         return true;
297     }
298
299     /**
300      * Adds a file to the list of attachments.
301      *
302      * @param  string  $file        The file name of the file to attach
303      *                              OR the file contents itself
304      * @param  string  $c_type      The content type
305      * @param  string  $name        The filename of the attachment
306      *                              Only use if $file is the contents
307      * @param  bool    $isfile      Whether $file is a filename or not
308      *                              Defaults to true
309      * @param  string  $encoding    The type of encoding to use.
310      *                              Defaults to base64.
311      *                              Possible values: 7bit, 8bit, base64, 
312      *                              or quoted-printable.
313      * @param  string  $disposition The content-disposition of this file
314      *                              Defaults to attachment.
315      *                              Possible values: attachment, inline.
316      * @param  string  $charset     The character set used in the filename
317      *                              of this attachment.
318      * @return mixed true on success or PEAR_Error object
319      * @access public
320      */
321     function addAttachment($file, $c_type = 'application/octet-stream',
322                            $name = '', $isfile = true,
323                            $encoding = 'base64',
324                            $disposition = 'attachment', $charset = '')
325     {
326         $filedata = ($isfile === true) ? $this->_file2str($file)
327                                            : $file;
328         if ($isfile === true) {
329             // Force the name the user supplied, otherwise use $file
330             $filename = (!empty($name)) ? $name : $file;
331         } else {
332             $filename = $name;
333         }
334         if (empty($filename)) {
335             $err = PEAR::raiseError(
336               "The supplied filename for the attachment can't be empty"
337             );
338             return $err;
339         }
340         $filename = basename($filename);
341         if (PEAR::isError($filedata)) {
342             return $filedata;
343         }
344
345         $this->_parts[] = array(
346                                 'body'        => $filedata,
347                                 'name'        => $filename,
348                                 'c_type'      => $c_type,
349                                 'encoding'    => $encoding,
350                                 'charset'     => $charset,
351                                 'disposition' => $disposition
352                                );
353         return true;
354     }
355
356     /**
357      * Get the contents of the given file name as string
358      *
359      * @param  string  $file_name  path of file to process
360      * @return string  contents of $file_name
361      * @access private
362      */
363     function &_file2str($file_name)
364     {
365         if (!is_readable($file_name)) {
366             $err = PEAR::raiseError('File is not readable ' . $file_name);
367             return $err;
368         }
369         if (!$fd = fopen($file_name, 'rb')) {
370             $err = PEAR::raiseError('Could not open ' . $file_name);
371             return $err;
372         }
373         $filesize = filesize($file_name);
374         if ($filesize == 0){
375             $cont =  "";
376         }else{
377             if ($magic_quote_setting = get_magic_quotes_runtime()){
378                 set_magic_quotes_runtime(0);
379             }
380             $cont = fread($fd, $filesize);
381             if ($magic_quote_setting){
382                 set_magic_quotes_runtime($magic_quote_setting);
383             }
384         }
385         fclose($fd);
386         return $cont;
387     }
388
389     /**
390      * Adds a text subpart to the mimePart object and
391      * returns it during the build process.
392      *
393      * @param mixed    The object to add the part to, or
394      *                 null if a new object is to be created.
395      * @param string   The text to add.
396      * @return object  The text mimePart object
397      * @access private
398      */
399     function &_addTextPart(&$obj, $text)
400     {
401         $params['content_type'] = 'text/plain';
402         $params['encoding']     = $this->_build_params['text_encoding'];
403         $params['charset']      = $this->_build_params['text_charset'];
404         if (is_object($obj)) {
405             $ret = $obj->addSubpart($text, $params);
406             return $ret;
407         } else {
408             $ret = new Mail_mimePart($text, $params);
409             return $ret;
410         }
411     }
412
413     /**
414      * Adds a html subpart to the mimePart object and
415      * returns it during the build process.
416      *
417      * @param  mixed   The object to add the part to, or
418      *                 null if a new object is to be created.
419      * @return object  The html mimePart object
420      * @access private
421      */
422     function &_addHtmlPart(&$obj)
423     {
424         $params['content_type'] = 'text/html';
425         $params['encoding']     = $this->_build_params['html_encoding'];
426         $params['charset']      = $this->_build_params['html_charset'];
427         if (is_object($obj)) {
428             $ret = $obj->addSubpart($this->_htmlbody, $params);
429             return $ret;
430         } else {
431             $ret = new Mail_mimePart($this->_htmlbody, $params);
432             return $ret;
433         }
434     }
435
436     /**
437      * Creates a new mimePart object, using multipart/mixed as
438      * the initial content-type and returns it during the
439      * build process.
440      *
441      * @return object  The multipart/mixed mimePart object
442      * @access private
443      */
444     function &_addMixedPart()
445     {
446         $params['content_type'] = 'multipart/mixed';
447         $ret = new Mail_mimePart('', $params);
448         return $ret;
449     }
450
451     /**
452      * Adds a multipart/alternative part to a mimePart
453      * object (or creates one), and returns it during
454      * the build process.
455      *
456      * @param  mixed   The object to add the part to, or
457      *                 null if a new object is to be created.
458      * @return object  The multipart/mixed mimePart object
459      * @access private
460      */
461     function &_addAlternativePart(&$obj)
462     {
463         $params['content_type'] = 'multipart/alternative';
464         if (is_object($obj)) {
465             return $obj->addSubpart('', $params);
466         } else {
467             $ret = new Mail_mimePart('', $params);
468             return $ret;
469         }
470     }
471
472     /**
473      * Adds a multipart/related part to a mimePart
474      * object (or creates one), and returns it during
475      * the build process.
476      *
477      * @param mixed    The object to add the part to, or
478      *                 null if a new object is to be created
479      * @return object  The multipart/mixed mimePart object
480      * @access private
481      */
482     function &_addRelatedPart(&$obj)
483     {
484         $params['content_type'] = 'multipart/related';
485         if (is_object($obj)) {
486             return $obj->addSubpart('', $params);
487         } else {
488             $ret = new Mail_mimePart('', $params);
489             return $ret;
490         }
491     }
492
493     /**
494      * Adds an html image subpart to a mimePart object
495      * and returns it during the build process.
496      *
497      * @param  object  The mimePart to add the image to
498      * @param  array   The image information
499      * @return object  The image mimePart object
500      * @access private
501      */
502     function &_addHtmlImagePart(&$obj, $value)
503     {
504         $params['content_type'] = $value['c_type'] . '; ' .
505                                   'name="' . $value['name'] . '"';
506         $params['encoding']     = 'base64';
507         $params['disposition']  = 'inline';
508         $params['dfilename']    = $value['name'];
509         $params['cid']          = $value['cid'];
510         $ret = $obj->addSubpart($value['body'], $params);
511         return $ret;
512         
513     }
514
515     /**
516      * Adds an attachment subpart to a mimePart object
517      * and returns it during the build process.
518      *
519      * @param  object  The mimePart to add the image to
520      * @param  array   The attachment information
521      * @return object  The image mimePart object
522      * @access private
523      */
524     function &_addAttachmentPart(&$obj, $value)
525     {
526         $params['dfilename']    = $value['name'];
527         $params['encoding']     = $value['encoding'];
528         if ($value['disposition'] != "inline") {
529             $fname = array("fname" => $value['name']);
530             $fname_enc = $this->_encodeHeaders($fname, array('head_charset' => $value['charset'] ? $value['charset'] : 'iso-8859-1'));
531             $params['dfilename'] = $fname_enc['fname'];
532         }
533         if ($value['charset']) {
534             $params['charset'] = $value['charset'];
535         }
536         $params['content_type'] = $value['c_type'] . '; ' .
537                                   'name="' . $params['dfilename'] . '"';
538         $params['disposition']  = isset($value['disposition']) ? 
539                                   $value['disposition'] : 'attachment';
540         $ret = $obj->addSubpart($value['body'], $params);
541         return $ret;
542     }
543
544     /**
545      * Returns the complete e-mail, ready to send using an alternative
546      * mail delivery method. Note that only the mailpart that is made
547      * with Mail_Mime is created. This means that,
548      * YOU WILL HAVE NO TO: HEADERS UNLESS YOU SET IT YOURSELF 
549      * using the $xtra_headers parameter!
550      * 
551      * @param  string $separation   The separation etween these two parts.
552      * @param  array  $build_params The Build parameters passed to the
553      *                              &get() function. See &get for more info.
554      * @param  array  $xtra_headers The extra headers that should be passed
555      *                              to the &headers() function.
556      *                              See that function for more info.
557      * @param  bool   $overwrite    Overwrite the existing headers with new.
558      * @return string The complete e-mail.
559      * @access public
560      */
561     function getMessage($separation = null, $build_params = null, $xtra_headers = null, $overwrite = false)
562     {
563         if ($separation === null)
564         {
565             $separation = MAIL_MIME_CRLF;
566         }
567         $body = $this->get($build_params);
568         $head = $this->txtHeaders($xtra_headers, $overwrite);
569         $mail = $head . $separation . $body;
570         return $mail;
571     }
572
573
574     /**
575      * Builds the multipart message from the list ($this->_parts) and
576      * returns the mime content.
577      *
578      * @param  array  Build parameters that change the way the email
579      *                is built. Should be associative. Can contain:
580      *                head_encoding  -  What encoding to use for the headers. 
581      *                                  Options: quoted-printable or base64
582      *                                  Default is quoted-printable
583      *                text_encoding  -  What encoding to use for plain text
584      *                                  Options: 7bit, 8bit, base64, or quoted-printable
585      *                                  Default is 7bit
586      *                html_encoding  -  What encoding to use for html
587      *                                  Options: 7bit, 8bit, base64, or quoted-printable
588      *                                  Default is quoted-printable
589      *                7bit_wrap      -  Number of characters before text is
590      *                                  wrapped in 7bit encoding
591      *                                  Default is 998
592      *                html_charset   -  The character set to use for html.
593      *                                  Default is iso-8859-1
594      *                text_charset   -  The character set to use for text.
595      *                                  Default is iso-8859-1
596      *                head_charset   -  The character set to use for headers.
597      *                                  Default is iso-8859-1
598      * @return string The mime content
599      * @access public
600      */
601     function &get($build_params = null)
602     {
603         if (isset($build_params)) {
604             while (list($key, $value) = each($build_params)) {
605                 $this->_build_params[$key] = $value;
606             }
607         }
608
609         if (!empty($this->_html_images) AND isset($this->_htmlbody)) {
610             foreach ($this->_html_images as $key => $value) {
611                 $regex = array();
612                 $regex[] = '#(\s)((?i)src|background|href(?-i))\s*=\s*(["\']?)' .
613                             preg_quote($value['name'], '#') . '\3#';
614                 $regex[] = '#(?i)url(?-i)\(\s*(["\']?)' .
615                             preg_quote($value['name'], '#') . '\1\s*\)#';
616                 $rep = array();
617                 $rep[] = '\1\2=\3cid:' . $value['cid'] .'\3';
618                 $rep[] = 'url(\1cid:' . $value['cid'] . '\2)';
619                 $this->_htmlbody = preg_replace($regex, $rep,
620                                        $this->_htmlbody
621                                    );
622                 $this->_html_images[$key]['name'] = basename($this->_html_images[$key]['name']);
623             }
624         }
625
626         $null        = null;
627         $attachments = !empty($this->_parts)                ? true : false;
628         $html_images = !empty($this->_html_images)          ? true : false;
629         $html        = !empty($this->_htmlbody)             ? true : false;
630         $text        = (!$html AND !empty($this->_txtbody)) ? true : false;
631
632         switch (true) {
633         case $text AND !$attachments:
634             $message =& $this->_addTextPart($null, $this->_txtbody);
635             break;
636
637         case !$text AND !$html AND $attachments:
638             $message =& $this->_addMixedPart();
639             for ($i = 0; $i < count($this->_parts); $i++) {
640                 $this->_addAttachmentPart($message, $this->_parts[$i]);
641             }
642             break;
643
644         case $text AND $attachments:
645             $message =& $this->_addMixedPart();
646             $this->_addTextPart($message, $this->_txtbody);
647             for ($i = 0; $i < count($this->_parts); $i++) {
648                 $this->_addAttachmentPart($message, $this->_parts[$i]);
649             }
650             break;
651
652         case $html AND !$attachments AND !$html_images:
653             if (isset($this->_txtbody)) {
654                 $message =& $this->_addAlternativePart($null);
655                 $this->_addTextPart($message, $this->_txtbody);
656                 $this->_addHtmlPart($message);
657             } else {
658                 $message =& $this->_addHtmlPart($null);
659             }
660             break;
661
662         case $html AND !$attachments AND $html_images:
663             if (isset($this->_txtbody)) {
664                 $message =& $this->_addAlternativePart($null);
665                 $this->_addTextPart($message, $this->_txtbody);
666                 $related =& $this->_addRelatedPart($message);
667             } else {
668                 $message =& $this->_addRelatedPart($null);
669                 $related =& $message;
670             }
671             $this->_addHtmlPart($related);
672             for ($i = 0; $i < count($this->_html_images); $i++) {
673                 $this->_addHtmlImagePart($related, $this->_html_images[$i]);
674             }
675             break;
676
677         case $html AND $attachments AND !$html_images:
678             $message =& $this->_addMixedPart();
679             if (isset($this->_txtbody)) {
680                 $alt =& $this->_addAlternativePart($message);
681                 $this->_addTextPart($alt, $this->_txtbody);
682                 $this->_addHtmlPart($alt);
683             } else {
684                 $this->_addHtmlPart($message);
685             }
686             for ($i = 0; $i < count($this->_parts); $i++) {
687                 $this->_addAttachmentPart($message, $this->_parts[$i]);
688             }
689             break;
690
691         case $html AND $attachments AND $html_images:
692             $message =& $this->_addMixedPart();
693             if (isset($this->_txtbody)) {
694                 $alt =& $this->_addAlternativePart($message);
695                 $this->_addTextPart($alt, $this->_txtbody);
696                 $rel =& $this->_addRelatedPart($alt);
697             } else {
698                 $rel =& $this->_addRelatedPart($message);
699             }
700             $this->_addHtmlPart($rel);
701             for ($i = 0; $i < count($this->_html_images); $i++) {
702                 $this->_addHtmlImagePart($rel, $this->_html_images[$i]);
703             }
704             for ($i = 0; $i < count($this->_parts); $i++) {
705                 $this->_addAttachmentPart($message, $this->_parts[$i]);
706             }
707             break;
708
709         }
710
711         if (isset($message)) {
712             $output = $message->encode();
713             $this->_headers = array_merge($this->_headers,
714                                           $output['headers']);
715             $body = $output['body'];
716             return $body;
717
718         } else {
719             $ret = false;
720             return $ret;
721         }
722     }
723
724     /**
725      * Returns an array with the headers needed to prepend to the email
726      * (MIME-Version and Content-Type). Format of argument is:
727      * $array['header-name'] = 'header-value';
728      *
729      * @param  array $xtra_headers Assoc array with any extra headers.
730      *                             Optional.
731      * @param  bool  $overwrite    Overwrite already existing headers.
732      * @return array Assoc array with the mime headers
733      * @access public
734      */
735     function &headers($xtra_headers = null, $overwrite = false)
736     {
737         // Content-Type header should already be present,
738         // So just add mime version header
739         $headers['MIME-Version'] = '1.0';
740         if (isset($xtra_headers)) {
741             $headers = array_merge($headers, $xtra_headers);
742         }
743         if ($overwrite){
744             $this->_headers = array_merge($this->_headers, $headers);
745         }else{
746             $this->_headers = array_merge($headers, $this->_headers);
747         }
748
749         $encodedHeaders = $this->_encodeHeaders($this->_headers);
750         return $encodedHeaders;
751     }
752
753     /**
754      * Get the text version of the headers
755      * (usefull if you want to use the PHP mail() function)
756      *
757      * @param  array   $xtra_headers Assoc array with any extra headers.
758      *                               Optional.
759      * @param  bool    $overwrite    Overwrite the existing heaers with new.
760      * @return string  Plain text headers
761      * @access public
762      */
763     function txtHeaders($xtra_headers = null, $overwrite = false)
764     {
765         $headers = $this->headers($xtra_headers, $overwrite);
766         $ret = '';
767         foreach ($headers as $key => $val) {
768             $ret .= "$key: $val" . MAIL_MIME_CRLF;
769         }
770         return $ret;
771     }
772
773     /**
774      * Sets the Subject header
775      *
776      * @param  string $subject String to set the subject to
777      * access  public
778      */
779     function setSubject($subject)
780     {
781         $this->_headers['Subject'] = $subject;
782     }
783
784     /**
785      * Set an email to the From (the sender) header
786      *
787      * @param  string $email The email direction to add
788      * @access public
789      */
790     function setFrom($email)
791     {
792         $this->_headers['From'] = $email;
793     }
794
795     /**
796      * Add an email to the Cc (carbon copy) header
797      * (multiple calls to this method are allowed)
798      *
799      * @param  string $email The email direction to add
800      * @access public
801      */
802     function addCc($email)
803     {
804         if (isset($this->_headers['Cc'])) {
805             $this->_headers['Cc'] .= ", $email";
806         } else {
807             $this->_headers['Cc'] = $email;
808         }
809     }
810
811     /**
812      * Add an email to the Bcc (blank carbon copy) header
813      * (multiple calls to this method are allowed)
814      *
815      * @param  string $email The email direction to add
816      * @access public
817      */
818     function addBcc($email)
819     {
820         if (isset($this->_headers['Bcc'])) {
821             $this->_headers['Bcc'] .= ", $email";
822         } else {
823             $this->_headers['Bcc'] = $email;
824         }
825     }
826
827     /**
828      * Since the PHP send function requires you to specifiy 
829      * recipients (To: header) separately from the other
830      * headers, the To: header is not properly encoded.
831      * To fix this, you can use this public method to 
832      * encode your recipients before sending to the send
833      * function
834      *
835      * @param  string $recipients A comma-delimited list of recipients
836      * @return string Encoded data
837      * @access public
838      */
839     function encodeRecipients($recipients)
840     {
841         $input = array("To" => $recipients);
842         $retval = $this->_encodeHeaders($input);
843         return $retval["To"] ;
844     }
845
846     /**
847      * Encodes a header as per RFC2047
848      *
849      * @param  array $input  The header data to encode
850      * @param  array $params Extra build parameters
851      * @return array Encoded data
852      * @access private
853      */
854     function _encodeHeaders($input, $params = array())
855     {
856         
857         $build_params = $this->_build_params;
858         while (list($key, $value) = each($params)) {
859             $build_params[$key] = $value;
860         }
861         
862         foreach ($input as $hdr_name => $hdr_value) {
863             $hdr_vals = preg_split("|(\s)|", $hdr_value, -1, PREG_SPLIT_DELIM_CAPTURE);
864             $hdr_value_out="";
865             $previous = "";
866             foreach ($hdr_vals as $hdr_val){
867                 if (!trim($hdr_val)){
868                     //whitespace needs to be handled with another string, or it
869                     //won't show between encoded strings. Prepend this to the next item.
870                     $previous .= $hdr_val;
871                     continue;
872                 }else{
873                     $hdr_val = $previous . $hdr_val;
874                     $previous = "";
875                 }
876                 if (function_exists('iconv_mime_encode') && preg_match('#[\x80-\xFF]{1}#', $hdr_val)){
877                     $imePref = array();
878                     if ($build_params['head_encoding'] == 'base64'){
879                         $imePrefs['scheme'] = 'B';
880                     }else{
881                         $imePrefs['scheme'] = 'Q';
882                     }
883                     $imePrefs['input-charset']  = $build_params['head_charset'];
884                     $imePrefs['output-charset'] = $build_params['head_charset'];
885                     $hdr_val = iconv_mime_encode($hdr_name, $hdr_val, $imePrefs);
886                     $hdr_val = preg_replace("#^{$hdr_name}\:\ #", "", $hdr_val);
887                 }elseif (preg_match('#[\x80-\xFF]{1}#', $hdr_val)){
888                     //This header contains non ASCII chars and should be encoded.
889                     switch ($build_params['head_encoding']) {
890                     case 'base64':
891                         //Base64 encoding has been selected.
892                         
893                         //Generate the header using the specified params and dynamicly 
894                         //determine the maximum length of such strings.
895                         //75 is the value specified in the RFC. The first -2 is there so 
896                         //the later regexp doesn't break any of the translated chars.
897                         //The -2 on the first line-regexp is to compensate for the ": "
898                         //between the header-name and the header value
899                         $prefix = '=?' . $build_params['head_charset'] . '?B?';
900                         $suffix = '?=';
901                         $maxLength = 75 - strlen($prefix . $suffix) - 2;
902                         $maxLength1stLine = $maxLength - strlen($hdr_name) - 2;
903                         
904                         //Base64 encode the entire string
905                         $hdr_val = base64_encode($hdr_val);
906                         
907                         //This regexp will break base64-encoded text at every 
908                         //$maxLength but will not break any encoded letters.
909                         $reg1st = "|.{0,$maxLength1stLine}[^\=][^\=]|";
910                         $reg2nd = "|.{0,$maxLength}[^\=][^\=]|";
911                         break;
912                     case 'quoted-printable':
913                     default:
914                         //quoted-printable encoding has been selected
915                         
916                         //Generate the header using the specified params and dynamicly 
917                         //determine the maximum length of such strings.
918                         //75 is the value specified in the RFC. The -2 is there so 
919                         //the later regexp doesn't break any of the translated chars.
920                         //The -2 on the first line-regexp is to compensate for the ": "
921                         //between the header-name and the header value
922                         $prefix = '=?' . $build_params['head_charset'] . '?Q?';
923                         $suffix = '?=';
924                         $maxLength = 75 - strlen($prefix . $suffix) - 2;
925                         $maxLength1stLine = $maxLength - strlen($hdr_name) - 2;
926                         
927                         //Replace all special characters used by the encoder.
928                         $search  = array("=",   "_",   "?",   " ");
929                         $replace = array("=3D", "=5F", "=3F", "_");
930                         $hdr_val = str_replace($search, $replace, $hdr_val);
931                         
932                         //Replace all extended characters (\x80-xFF) with their
933                         //ASCII values.
934                         $hdr_val = preg_replace(
935                             '#([\x80-\xFF])#e',
936                             '"=" . strtoupper(dechex(ord("\1")))',
937                             $hdr_val
938                         );
939                         //This regexp will break QP-encoded text at every $maxLength
940                         //but will not break any encoded letters.
941                         $reg1st = "|(.{0,$maxLength1stLine})[^\=]|";
942                         $reg2nd = "|(.{0,$maxLength})[^\=]|";
943                         break;
944                     }
945                     //Begin with the regexp for the first line.
946                     $reg = $reg1st;
947                     //Prevent lins that are just way to short;
948                     if ($maxLength1stLine >1){
949                         $reg = $reg2nd;
950                     }
951                     $output = "";
952                     while ($hdr_val) {
953                         //Split translated string at every $maxLength
954                         //But make sure not to break any translated chars.
955                         $found = preg_match($reg, $hdr_val, $matches);
956                         
957                         //After this first line, we need to use a different
958                         //regexp for the first line.
959                         $reg = $reg2nd;
960                         
961                         //Save the found part and encapsulate it in the
962                         //prefix & suffix. Then remove the part from the
963                         //$hdr_val variable.
964                         if ($found){
965                             $part = $matches[0];
966                             $hdr_val = substr($hdr_val, strlen($matches[0]));
967                         }else{
968                             $part = $hdr_val;
969                             $hdr_val = "";
970                         }
971                         
972                         //RFC 2047 specifies that any split header should be seperated
973                         //by a CRLF SPACE. 
974                         if ($output){
975                             $output .=  "\r\n ";
976                         }
977                         $output .= $prefix . $part . $suffix;
978                     }
979                     $hdr_val = $output;
980                 }
981                 $hdr_value_out .= $hdr_val;
982             }
983             $input[$hdr_name] = $hdr_value_out;
984         }
985
986         return $input;
987     }
988
989     /**
990      * Set the object's end-of-line and define the constant if applicable
991      *
992      * @param string $eol End Of Line sequence
993      * @access private
994      */
995     function _setEOL($eol)
996     {
997         $this->_eol = $eol;
998         if (!defined('MAIL_MIME_CRLF')) {
999             define('MAIL_MIME_CRLF', $this->_eol, true);
1000         }
1001     }
1002
1003     
1004
1005 } // End of class