source: trunk/library/mime/mime.php @ 7673

Revision 7673, 50.4 KB checked in by douglasz, 11 years ago (diff)

Ticket #3236 - Correcoes para Performance: Function Within Loop Declaration.

  • Property svn:executable set to *
Line 
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 * @author    Aleksander Machniak <alec@php.net>
49 * @copyright 2003-2006 PEAR <pear-group@php.net>
50 * @license   http://www.opensource.org/licenses/bsd-license.php BSD License
51 * @version   CVS: $Id: mime.php 305690 2010-11-23 12:41:00Z alec $
52 * @link      http://pear.php.net/package/Mail_mime
53 *
54 *            This class is based on HTML Mime Mail class from
55 *            Richard Heyes <richard@phpguru.org> which was based also
56 *            in the mime_mail.class by Tobias Ratschiller <tobias@dnet.it>
57 *            and Sascha Schumann <sascha@schumann.cx>
58 */
59
60
61/**
62 * require PEAR
63 *
64 * This package depends on PEAR to raise errors.
65 */
66require_once dirname(__FILE__).'/../PEAR/PEAR.php';
67
68/**
69 * require Mail_mimePart
70 *
71 * Mail_mimePart contains the code required to
72 * create all the different parts a mail can
73 * consist of.
74 */
75//require_once 'Mail/mimePart.php';
76
77
78/**
79 * The Mail_Mime class provides an OO interface to create MIME
80 * enabled email messages. This way you can create emails that
81 * contain plain-text bodies, HTML bodies, attachments, inline
82 * images and specific headers.
83 *
84 * @category  Mail
85 * @package   Mail_Mime
86 * @author    Richard Heyes  <richard@phpguru.org>
87 * @author    Tomas V.V. Cox <cox@idecnet.com>
88 * @author    Cipriano Groenendal <cipri@php.net>
89 * @author    Sean Coates <sean@php.net>
90 * @copyright 2003-2006 PEAR <pear-group@php.net>
91 * @license   http://www.opensource.org/licenses/bsd-license.php BSD License
92 * @version   Release: @package_version@
93 * @link      http://pear.php.net/package/Mail_mime
94 */
95class Mail_mime
96{
97    /**
98     * Contains the plain text part of the email
99     *
100     * @var string
101     * @access private
102     */
103    var $_txtbody;
104
105    /**
106     * Contains the html part of the email
107     *
108     * @var string
109     * @access private
110     */
111    var $_htmlbody;
112
113    /**
114     * list of the attached images
115     *
116     * @var array
117     * @access private
118     */
119    var $_html_images = array();
120
121    /**
122     * list of the attachements
123     *
124     * @var array
125     * @access private
126     */
127    var $_parts = array();
128
129    /**
130     * Headers for the mail
131     *
132     * @var array
133     * @access private
134     */
135    var $_headers = array();
136
137    /**
138     * Build parameters
139     *
140     * @var array
141     * @access private
142     */
143    var $_build_params = array(
144        // What encoding to use for the headers
145        // Options: quoted-printable or base64
146        'head_encoding' => 'quoted-printable',
147        // What encoding to use for plain text
148        // Options: 7bit, 8bit, base64, or quoted-printable
149        'text_encoding' => 'quoted-printable',
150        // What encoding to use for html
151        // Options: 7bit, 8bit, base64, or quoted-printable
152        'html_encoding' => 'quoted-printable',
153        // The character set to use for html
154        'html_charset'  => 'ISO-8859-1',
155        // The character set to use for text
156        'text_charset'  => 'ISO-8859-1',
157        // The character set to use for headers
158        'head_charset'  => 'ISO-8859-1',
159        // End-of-line sequence
160        'eol'           => "\r\n",
161        // Delay attachment files IO until building the message
162        'delay_file_io' => false
163    );
164
165    /**
166     * Constructor function
167     *
168     * @param mixed $params Build parameters that change the way the email
169     *                      is built. Should be an associative array.
170     *                      See $_build_params.
171     *
172     * @return void
173     * @access public
174     */
175    function Mail_mime($params = array())
176    {
177        // Backward-compatible EOL setting
178        if (is_string($params)) {
179            $this->_build_params['eol'] = $params;
180        } else if (defined('MAIL_MIME_CRLF') && !isset($params['eol'])) {
181            $this->_build_params['eol'] = MAIL_MIME_CRLF;
182        }
183
184        // Update build parameters
185        if (!empty($params) && is_array($params)) {
186            while (list($key, $value) = each($params)) {
187                $this->_build_params[$key] = $value;
188            }
189        }
190    }
191
192    /**
193     * Set build parameter value
194     *
195     * @param string $name  Parameter name
196     * @param string $value Parameter value
197     *
198     * @return void
199     * @access public
200     * @since 1.6.0
201     */
202    function setParam($name, $value)
203    {
204        $this->_build_params[$name] = $value;
205    }
206
207    /**
208     * Get build parameter value
209     *
210     * @param string $name Parameter name
211     *
212     * @return mixed Parameter value
213     * @access public
214     * @since 1.6.0
215     */
216    function getParam($name)
217    {
218        return isset($this->_build_params[$name]) ? $this->_build_params[$name] : null;
219    }
220
221    /**
222     * Accessor function to set the body text. Body text is used if
223     * it's not an html mail being sent or else is used to fill the
224     * text/plain part that emails clients who don't support
225     * html should show.
226     *
227     * @param string $data   Either a string or
228     *                       the file name with the contents
229     * @param bool   $isfile If true the first param should be treated
230     *                       as a file name, else as a string (default)
231     * @param bool   $append If true the text or file is appended to
232     *                       the existing body, else the old body is
233     *                       overwritten
234     *
235     * @return mixed         True on success or PEAR_Error object
236     * @access public
237     */
238    function setTXTBody($data, $isfile = false, $append = false)
239    {
240        if (!$isfile) {
241            if (!$append) {
242                $this->_txtbody = $data;
243            } else {
244                $this->_txtbody .= $data;
245            }
246        } else {
247            $cont = $this->_file2str($data);
248            if (PEAR::isError($cont)) {
249                return $cont;
250            }
251            if (!$append) {
252                $this->_txtbody = $cont;
253            } else {
254                $this->_txtbody .= $cont;
255            }
256        }
257        return true;
258    }
259
260    /**
261     * Get message text body
262     *
263     * @return string Text body
264     * @access public
265     * @since 1.6.0
266     */
267    function getTXTBody()
268    {
269        return $this->_txtbody;
270    }
271
272    /**
273     * Adds a html part to the mail.
274     *
275     * @param string $data   Either a string or the file name with the
276     *                       contents
277     * @param bool   $isfile A flag that determines whether $data is a
278     *                       filename, or a string(false, default)
279     *
280     * @return bool          True on success
281     * @access public
282     */
283    function setHTMLBody($data, $isfile = false)
284    {
285        if (!$isfile) {
286            $this->_htmlbody = $data;
287        } else {
288            $cont = $this->_file2str($data);
289            if (PEAR::isError($cont)) {
290                return $cont;
291            }
292            $this->_htmlbody = $cont;
293        }
294
295        return true;
296    }
297
298    /**
299     * Get message HTML body
300     *
301     * @return string HTML body
302     * @access public
303     * @since 1.6.0
304     */
305    function getHTMLBody()
306    {
307        return $this->_htmlbody;
308    }
309
310    /**
311     * Adds an image to the list of embedded images.
312     *
313     * @param string $file       The image file name OR image data itself
314     * @param string $c_type     The content type
315     * @param string $name       The filename of the image.
316     *                           Only used if $file is the image data.
317     * @param bool   $isfile     Whether $file is a filename or not.
318     *                           Defaults to true
319     * @param string $content_id Desired Content-ID of MIME part
320     *                           Defaults to generated unique ID
321     *
322     * @return bool          True on success
323     * @access public
324     */
325    function addHTMLImage($file,
326        $c_type='application/octet-stream',
327        $name = '',
328        $isfile = true,
329        $content_id = null
330    ) {
331        $bodyfile = null;
332
333        if ($isfile) {
334            // Don't load file into memory
335            if ($this->_build_params['delay_file_io']) {
336                $filedata = null;
337                $bodyfile = $file;
338            } else {
339                if (PEAR::isError($filedata = $this->_file2str($file))) {
340                    return $filedata;
341                }
342            }
343            $filename = ($name ? $name : $file);
344        } else {
345            $filedata = $file;
346            $filename = $name;
347        }
348
349        if (!$content_id) {
350            $content_id = md5(uniqid(time()));
351        }
352
353        $this->_html_images[] = array(
354            'body'      => $filedata,
355            'body_file' => $bodyfile,
356            'name'      => $filename,
357            'c_type'    => $c_type,
358            'cid'       => $content_id
359        );
360
361        return true;
362    }
363
364    /**
365     * Adds a file to the list of attachments.
366     *
367     * @param string $file        The file name of the file to attach
368     *                            or the file contents itself
369     * @param string $c_type      The content type
370     * @param string $name        The filename of the attachment
371     *                            Only use if $file is the contents
372     * @param bool   $isfile      Whether $file is a filename or not. Defaults to true
373     * @param string $encoding    The type of encoding to use. Defaults to base64.
374     *                            Possible values: 7bit, 8bit, base64 or quoted-printable.
375     * @param string $disposition The content-disposition of this file
376     *                            Defaults to attachment.
377     *                            Possible values: attachment, inline.
378     * @param string $charset     The character set of attachment's content.
379     * @param string $language    The language of the attachment
380     * @param string $location    The RFC 2557.4 location of the attachment
381     * @param string $n_encoding  Encoding of the attachment's name in Content-Type
382     *                            By default filenames are encoded using RFC2231 method
383     *                            Here you can set RFC2047 encoding (quoted-printable
384     *                            or base64) instead
385     * @param string $f_encoding  Encoding of the attachment's filename
386     *                            in Content-Disposition header.
387     * @param string $description Content-Description header
388     * @param string $h_charset   The character set of the headers e.g. filename
389     *                            If not specified, $charset will be used
390     *
391     * @return mixed              True on success or PEAR_Error object
392     * @access public
393     */
394    function addAttachment($file,
395        $c_type      = 'application/octet-stream',
396        $name        = '',
397        $isfile      = true,
398        $encoding    = 'base64',
399        $disposition = 'attachment',
400        $charset     = '',
401        $language    = '',
402        $location    = '',
403        $n_encoding  = null,
404        $f_encoding  = null,
405        $description = '',
406        $h_charset   = null
407    ) {
408        $bodyfile = null;
409
410        if ($isfile) {
411            // Don't load file into memory
412            if ($this->_build_params['delay_file_io']) {
413                $filedata = null;
414                $bodyfile = $file;
415            } else {
416                if (PEAR::isError($filedata = $this->_file2str($file))) {
417                    return $filedata;
418                }
419            }
420            // Force the name the user supplied, otherwise use $file
421            $filename = ($name ? $name : $this->_basename($file ));       
422        } else {
423            $filedata = $file;
424            $filename = $name;
425        }
426
427        if (!strlen($filename)) {
428            $msg = "The supplied filename for the attachment can't be empty";
429            $err = PEAR::raiseError($msg);
430            return $err;
431        }
432       
433        $this->_parts[] = array(
434            'body'        => $filedata,
435            'body_file'   => $bodyfile,
436            'name'        => $filename,
437            'c_type'      => $c_type,
438            'charset'     => $charset,
439            'encoding'    => $encoding,
440            'language'    => $language,
441            'location'    => $location,
442            'disposition' => $disposition,
443            'description' => $description,
444            'name_encoding'     => $n_encoding,
445            'filename_encoding' => $f_encoding,
446            'headers_charset'   => $h_charset,
447        );
448
449        return true;
450    }
451
452    /**
453     * Get the contents of the given file name as string
454     *
455     * @param string $file_name Path of file to process
456     *
457     * @return string           Contents of $file_name
458     * @access private
459     */
460    function &_file2str($file_name)
461    {
462        // Check state of file and raise an error properly
463        if (!file_exists($file_name)) {
464            $err = PEAR::raiseError('File not found: ' . $file_name);
465            return $err;
466        }
467        if (!is_file($file_name)) {
468            $err = PEAR::raiseError('Not a regular file: ' . $file_name);
469            return $err;
470        }
471        if (!is_readable($file_name)) {
472            $err = PEAR::raiseError('File is not readable: ' . $file_name);
473            return $err;
474        }
475
476        // Temporarily reset magic_quotes_runtime and read file contents
477        if ($magic_quote_setting = get_magic_quotes_runtime()) {
478            @ini_set('magic_quotes_runtime', 0);
479        }
480        $cont = file_get_contents($file_name);
481        if ($magic_quote_setting) {
482            @ini_set('magic_quotes_runtime', $magic_quote_setting);
483        }
484
485        return $cont;
486    }
487
488    /**
489     * Adds a text subpart to the mimePart object and
490     * returns it during the build process.
491     *
492     * @param mixed  &$obj The object to add the part to, or
493     *                     null if a new object is to be created.
494     * @param string $text The text to add.
495     *
496     * @return object      The text mimePart object
497     * @access private
498     */
499    function &_addTextPart(&$obj, $text)
500    {
501        $params['content_type'] = 'text/plain';
502        $params['encoding']     = $this->_build_params['text_encoding'];
503        $params['charset']      = $this->_build_params['text_charset'];
504        $params['eol']          = $this->_build_params['eol'];
505
506        if (is_object($obj)) {
507            $ret = $obj->addSubpart($text, $params);
508            return $ret;
509        } else {
510            $ret = new Mail_mimePart($text, $params);
511            return $ret;
512        }
513    }
514
515    /**
516     * Adds a html subpart to the mimePart object and
517     * returns it during the build process.
518     *
519     * @param mixed &$obj The object to add the part to, or
520     *                    null if a new object is to be created.
521     *
522     * @return object     The html mimePart object
523     * @access private
524     */
525    function &_addHtmlPart(&$obj)
526    {
527        $params['content_type'] = 'text/html';
528        $params['encoding']     = $this->_build_params['html_encoding'];
529        $params['charset']      = $this->_build_params['html_charset'];
530        $params['eol']          = $this->_build_params['eol'];
531
532        if (is_object($obj)) {
533            $ret = $obj->addSubpart($this->_htmlbody, $params);
534            return $ret;
535        } else {
536            $ret = new Mail_mimePart($this->_htmlbody, $params);
537            return $ret;
538        }
539    }
540
541    /**
542     * Creates a new mimePart object, using multipart/mixed as
543     * the initial content-type and returns it during the
544     * build process.
545     *
546     * @return object The multipart/mixed mimePart object
547     * @access private
548     */
549    function &_addMixedPart()
550    {
551        $params                 = array();
552        $params['content_type'] = 'multipart/mixed';
553        $params['eol']          = $this->_build_params['eol'];
554
555        // Create empty multipart/mixed Mail_mimePart object to return
556        $ret = new Mail_mimePart('', $params);
557        return $ret;
558    }
559
560    /**
561     * Adds a multipart/alternative part to a mimePart
562     * object (or creates one), and returns it during
563     * the build process.
564     *
565     * @param mixed &$obj The object to add the part to, or
566     *                    null if a new object is to be created.
567     *
568     * @return object     The multipart/mixed mimePart object
569     * @access private
570     */
571    function &_addAlternativePart(&$obj)
572    {
573        $params['content_type'] = 'multipart/alternative';
574        $params['eol']          = $this->_build_params['eol'];
575
576        if (is_object($obj)) {
577            return $obj->addSubpart('', $params);
578        } else {
579            $ret = new Mail_mimePart('', $params);
580            return $ret;
581        }
582    }
583
584    /**
585     * Adds a multipart/related part to a mimePart
586     * object (or creates one), and returns it during
587     * the build process.
588     *
589     * @param mixed &$obj The object to add the part to, or
590     *                    null if a new object is to be created
591     *
592     * @return object     The multipart/mixed mimePart object
593     * @access private
594     */
595    function &_addRelatedPart(&$obj)
596    {
597        $params['content_type'] = 'multipart/related';
598        $params['eol']          = $this->_build_params['eol'];
599
600        if (is_object($obj)) {
601            return $obj->addSubpart('', $params);
602        } else {
603            $ret = new Mail_mimePart('', $params);
604            return $ret;
605        }
606    }
607
608    /**
609     * Adds an html image subpart to a mimePart object
610     * and returns it during the build process.
611     *
612     * @param object &$obj  The mimePart to add the image to
613     * @param array  $value The image information
614     *
615     * @return object       The image mimePart object
616     * @access private
617     */
618    function &_addHtmlImagePart(&$obj, $value)
619    {
620        $params['content_type'] = $value['c_type'];
621        $params['encoding']     = 'base64';
622        $params['disposition']  = 'inline';
623        $params['filename']     = $value['name'];
624        $params['cid']          = $value['cid'];
625        $params['body_file']    = $value['body_file'];
626        $params['eol']          = $this->_build_params['eol'];
627
628        if (!empty($value['name_encoding'])) {
629            $params['name_encoding'] = $value['name_encoding'];
630        }
631        if (!empty($value['filename_encoding'])) {
632            $params['filename_encoding'] = $value['filename_encoding'];
633        }
634
635        $ret = $obj->addSubpart($value['body'], $params);
636        return $ret;
637    }
638
639    /**
640     * Adds an attachment subpart to a mimePart object
641     * and returns it during the build process.
642     *
643     * @param object &$obj  The mimePart to add the image to
644     * @param array  $value The attachment information
645     *
646     * @return object       The image mimePart object
647     * @access private
648     */
649    function &_addAttachmentPart(&$obj, $value)
650    {
651        $params['eol']          = $this->_build_params['eol'];
652        $params['filename']     = $value['name'];
653        $params['encoding']     = $value['encoding'];
654        $params['content_type'] = $value['c_type'];
655        $params['body_file']    = $value['body_file'];
656        $params['disposition']  = isset($value['disposition']) ?
657                                  $value['disposition'] : 'attachment';
658
659        // content charset
660        if (!empty($value['charset'])) {
661            $params['charset'] = $value['charset'];
662        }
663        // headers charset (filename, description)
664        if (!empty($value['headers_charset'])) {
665            $params['headers_charset'] = $value['headers_charset'];
666        }
667        if (!empty($value['language'])) {
668            $params['language'] = $value['language'];
669        }
670        if (!empty($value['location'])) {
671            $params['location'] = $value['location'];
672        }
673        if (!empty($value['name_encoding'])) {
674            $params['name_encoding'] = $value['name_encoding'];
675        }
676        if (!empty($value['filename_encoding'])) {
677            $params['filename_encoding'] = $value['filename_encoding'];
678        }
679        if (!empty($value['description'])) {
680            $params['description'] = $value['description'];
681        }
682
683        $ret = $obj->addSubpart($value['body'], $params);
684        return $ret;
685    }
686
687    /**
688     * Returns the complete e-mail, ready to send using an alternative
689     * mail delivery method. Note that only the mailpart that is made
690     * with Mail_Mime is created. This means that,
691     * YOU WILL HAVE NO TO: HEADERS UNLESS YOU SET IT YOURSELF
692     * using the $headers parameter!
693     *
694     * @param string $separation The separation between these two parts.
695     * @param array  $params     The Build parameters passed to the
696     *                           &get() function. See &get for more info.
697     * @param array  $headers    The extra headers that should be passed
698     *                           to the &headers() function.
699     *                           See that function for more info.
700     * @param bool   $overwrite  Overwrite the existing headers with new.
701     *
702     * @return mixed The complete e-mail or PEAR error object
703     * @access public
704     */
705    function getMessage($separation = null, $params = null, $headers = null,
706        $overwrite = false
707    ) {
708        if ($separation === null) {
709            $separation = $this->_build_params['eol'];
710        }
711
712        $body = $this->get($params);
713
714        if (PEAR::isError($body)) {
715            return $body;
716        }
717
718        $head = $this->txtHeaders($headers, $overwrite);
719        $mail = $head . $separation . $body;
720        return $mail;
721    }
722
723    /**
724     * Returns the complete e-mail body, ready to send using an alternative
725     * mail delivery method.
726     *
727     * @param array $params The Build parameters passed to the
728     *                      &get() function. See &get for more info.
729     *
730     * @return mixed The e-mail body or PEAR error object
731     * @access public
732     * @since 1.6.0
733     */
734    function getMessageBody($params = null)
735    {
736        return $this->get($params, null, true);
737    }
738
739    /**
740     * Writes (appends) the complete e-mail into file.
741     *
742     * @param string $filename  Output file location
743     * @param array  $params    The Build parameters passed to the
744     *                          &get() function. See &get for more info.
745     * @param array  $headers   The extra headers that should be passed
746     *                          to the &headers() function.
747     *                          See that function for more info.
748     * @param bool   $overwrite Overwrite the existing headers with new.
749     *
750     * @return mixed True or PEAR error object
751     * @access public
752     * @since 1.6.0
753     */
754    function saveMessage($filename, $params = null, $headers = null, $overwrite = false)
755    {
756        // Check state of file and raise an error properly
757        if (file_exists($filename) && !is_writable($filename)) {
758            $err = PEAR::raiseError('File is not writable: ' . $filename);
759            return $err;
760        }
761
762        // Temporarily reset magic_quotes_runtime and read file contents
763        if ($magic_quote_setting = get_magic_quotes_runtime()) {
764            @ini_set('magic_quotes_runtime', 0);
765        }
766
767        if (!($fh = fopen($filename, 'ab'))) {
768            $err = PEAR::raiseError('Unable to open file: ' . $filename);
769            return $err;
770        }
771
772        // Write message headers into file (skipping Content-* headers)
773        $head = $this->txtHeaders($headers, $overwrite, true);
774        if (fwrite($fh, $head) === false) {
775            $err = PEAR::raiseError('Error writing to file: ' . $filename);
776            return $err;
777        }
778
779        fclose($fh);
780
781        if ($magic_quote_setting) {
782            @ini_set('magic_quotes_runtime', $magic_quote_setting);
783        }
784
785        // Write the rest of the message into file
786        $res = $this->get($params, $filename);
787
788        return $res ? $res : true;
789    }
790
791    /**
792     * Writes (appends) the complete e-mail body into file.
793     *
794     * @param string $filename Output file location
795     * @param array  $params   The Build parameters passed to the
796     *                         &get() function. See &get for more info.
797     *
798     * @return mixed True or PEAR error object
799     * @access public
800     * @since 1.6.0
801     */
802    function saveMessageBody($filename, $params = null)
803    {
804        // Check state of file and raise an error properly
805        if (file_exists($filename) && !is_writable($filename)) {
806            $err = PEAR::raiseError('File is not writable: ' . $filename);
807            return $err;
808        }
809
810        // Temporarily reset magic_quotes_runtime and read file contents
811        if ($magic_quote_setting = get_magic_quotes_runtime()) {
812            @ini_set('magic_quotes_runtime', 0);
813        }
814
815        if (!($fh = fopen($filename, 'ab'))) {
816            $err = PEAR::raiseError('Unable to open file: ' . $filename);
817            return $err;
818        }
819
820        // Write the rest of the message into file
821        $res = $this->get($params, $filename, true);
822
823        return $res ? $res : true;
824    }
825
826    /**
827     * Builds the multipart message from the list ($this->_parts) and
828     * returns the mime content.
829     *
830     * @param array    $params    Build parameters that change the way the email
831     *                            is built. Should be associative. See $_build_params.
832     * @param resource $filename  Output file where to save the message instead of
833     *                            returning it
834     * @param boolean  $skip_head True if you want to return/save only the message
835     *                            without headers
836     *
837     * @return mixed The MIME message content string, null or PEAR error object
838     * @access public
839     */
840    function &get($params = null, $filename = null, $skip_head = false)
841    {
842        if (isset($params)) {
843            while (list($key, $value) = each($params)) {
844                $this->_build_params[$key] = $value;
845            }
846        }
847
848        if (isset($this->_headers['From'])) {
849            // Bug #11381: Illegal characters in domain ID
850            if (preg_match('#(@[0-9a-zA-Z\-\.]+)#', $this->_headers['From'], $matches)) {
851                $domainID = $matches[1];
852            } else {
853                $domainID = '@localhost';
854            }
855            foreach ($this->_html_images as $i => $img) {
856                $cid = $this->_html_images[$i]['cid'];
857                if (!preg_match('#'.preg_quote($domainID).'$#', $cid)) {
858                    $this->_html_images[$i]['cid'] = $cid . $domainID;
859                }
860            }
861        }
862
863        if (count($this->_html_images) && isset($this->_htmlbody)) {
864            foreach ($this->_html_images as $key => $value) {
865                $regex   = array();
866                $regex[] = '#(\s)((?i)src|background|href(?-i))\s*=\s*(["\']?)' .
867                            preg_quote($value['name'], '#') . '\3#';
868                $regex[] = '#(?i)url(?-i)\(\s*(["\']?)' .
869                            preg_quote($value['name'], '#') . '\1\s*\)#';
870
871                $rep   = array();
872                $rep[] = '\1\2=\3cid:' . $value['cid'] .'\3';
873                $rep[] = 'url(\1cid:' . $value['cid'] . '\1)';
874                             
875                $this->_htmlbody = preg_replace($regex, $rep, $this->_htmlbody);
876                $this->_html_images[$key]['name']
877                    = $this->_basename($this->_html_images[$key]['name']);
878                           
879            }
880        }
881
882        $this->_checkParams();
883
884        $null        = null;
885        $attachments = count($this->_parts)                 ? true : false;
886        $html_images = count($this->_html_images)           ? true : false;
887        $html        = strlen($this->_htmlbody)             ? true : false;
888        $text        = (!$html && strlen($this->_txtbody))  ? true : false;
889
890        switch (true) {
891        case $text && !$attachments:
892            $message =& $this->_addTextPart($null, $this->_txtbody);
893            break;
894
895        case !$text && !$html && $attachments:
896            $message =& $this->_addMixedPart();
897            $parts_count = count($this->_parts);
898            for ($i = 0; $i < $parts_count; ++$i) {
899                $this->_addAttachmentPart($message, $this->_parts[$i]);
900            }
901            break;
902
903        case $text && $attachments:
904            $message =& $this->_addMixedPart();
905            $this->_addTextPart($message, $this->_txtbody);
906            $parts_count = count($this->_parts);
907            for ($i = 0; $i < $parts_count; ++$i) {
908                $this->_addAttachmentPart($message, $this->_parts[$i]);
909            }
910            break;
911
912        case $html && !$attachments && !$html_images:
913            if (isset($this->_txtbody)) {
914                $message =& $this->_addAlternativePart($null);
915                $this->_addTextPart($message, $this->_txtbody);
916                $this->_addHtmlPart($message);
917            } else {
918                $message =& $this->_addHtmlPart($null);
919            }
920            break;
921
922        case $html && !$attachments && $html_images:
923            // * Content-Type: multipart/alternative;
924            //    * text
925            //    * Content-Type: multipart/related;
926            //       * html
927            //       * image...
928            if (isset($this->_txtbody)) {
929                $message =& $this->_addAlternativePart($null);
930                $this->_addTextPart($message, $this->_txtbody);
931
932                $ht =& $this->_addRelatedPart($message);
933                $this->_addHtmlPart($ht);
934                $html_images_count = count($this->_html_images);
935                for ($i = 0; $i < $html_images_count; ++$i) {
936                    $this->_addHtmlImagePart($ht, $this->_html_images[$i]);
937                }
938            } else {
939                // * Content-Type: multipart/related;
940                //    * html
941                //    * image...
942                $message =& $this->_addRelatedPart($null);
943                $this->_addHtmlPart($message);
944                $html_images_count = count($this->_html_images);
945                for ($i = 0; $i < $html_images_count; ++$i) {
946                    $this->_addHtmlImagePart($message, $this->_html_images[$i]);
947                }
948            }
949            /*
950            // #13444, #9725: the code below was a non-RFC compliant hack
951            // * Content-Type: multipart/related;
952            //    * Content-Type: multipart/alternative;
953            //        * text
954            //        * html
955            //    * image...
956            $message =& $this->_addRelatedPart($null);
957            if (isset($this->_txtbody)) {
958                $alt =& $this->_addAlternativePart($message);
959                $this->_addTextPart($alt, $this->_txtbody);
960                $this->_addHtmlPart($alt);
961            } else {
962                $this->_addHtmlPart($message);
963            }
964            $html_images_count = count($this->_html_images);
965            for ($i = 0; $i < $html_images_count; ++$i) {
966                $this->_addHtmlImagePart($message, $this->_html_images[$i]);
967            }
968            */
969            break;
970
971        case $html && $attachments && !$html_images:
972            $message =& $this->_addMixedPart();
973            if (isset($this->_txtbody)) {
974                $alt =& $this->_addAlternativePart($message);
975                $this->_addTextPart($alt, $this->_txtbody);
976                $this->_addHtmlPart($alt);
977            } else {
978                $this->_addHtmlPart($message);
979            }
980            $parts_count = count($this->_parts);
981            for ($i = 0; $i < $parts_count; ++$i) {
982                $this->_addAttachmentPart($message, $this->_parts[$i]);
983            }
984            break;
985                /*
986                 * Original:
987                 * case $html && $attachments && $html_images:
988            $message =& $this->_addMixedPart();
989            if (isset($this->_txtbody)) {
990                $alt =& $this->_addAlternativePart($message);
991                $this->_addTextPart($alt, $this->_txtbody);
992                $rel =& $this->_addRelatedPart($alt);
993            } else {
994                $rel =& $this->_addRelatedPart($message);
995            }
996            $this->_addHtmlPart($rel);
997                    $html_images_count = count($this->_html_images);
998            for ($i = 0; $i < $html_images_count; ++$i) {
999                $this->_addHtmlImagePart($rel, $this->_html_images[$i]);
1000            }
1001                    $parts_count = count($this->_parts);
1002            for ($i = 0; $i < $parts_count; ++$i) {
1003                $this->_addAttachmentPart($message, $this->_parts[$i]);
1004            }
1005                 *      break;
1006                 * Alteração feita para que as imagens senjam inseridas junto com os anexos e não em uma alternate part.
1007                 * Para compatibilização com a fomra do expresso anexar imagens no copo do e-mail
1008                 *
1009                 */
1010                case $html && $attachments && $html_images:
1011            $message =& $this->_addMixedPart();
1012            if (isset($this->_txtbody)) {
1013                $alt =& $this->_addAlternativePart($message);
1014                $this->_addTextPart($alt, $this->_txtbody);
1015            } else {
1016                                $this->_addHtmlPart($message);
1017            }
1018
1019                        $html_images_count = count($this->_html_images);
1020            for ($i = 0; $i < $html_images_count; ++$i) {
1021                $this->_addHtmlImagePart($message, $this->_html_images[$i]);
1022            }
1023            $parts_count = count($this->_parts);
1024            for ($i = 0; $i < $parts_count; ++$i) {
1025                $this->_addAttachmentPart($message, $this->_parts[$i]);
1026            }
1027            break;
1028                /*
1029                 * Fim da alteração
1030                 */     
1031
1032        }
1033
1034        if (!isset($message)) {
1035            $ret = null;
1036            return $ret;
1037        }
1038
1039        // Use saved boundary
1040        if (!empty($this->_build_params['boundary'])) {
1041            $boundary = $this->_build_params['boundary'];
1042        } else {
1043            $boundary = null;
1044        }
1045
1046        // Write output to file
1047        if ($filename) {
1048            // Append mimePart message headers and body into file
1049            $headers = $message->encodeToFile($filename, $boundary, $skip_head);
1050            if (PEAR::isError($headers)) {
1051                return $headers;
1052            }
1053            $this->_headers = array_merge($this->_headers, $headers);
1054            $ret = null;
1055            return $ret;
1056        } else {
1057            $output = $message->encode($boundary, $skip_head);
1058            if (PEAR::isError($output)) {
1059                return $output;
1060            }
1061            $this->_headers = array_merge($this->_headers, $output['headers']);
1062            $body = $output['body'];
1063            return $body;
1064        }
1065    }
1066
1067    /**
1068     * Returns an array with the headers needed to prepend to the email
1069     * (MIME-Version and Content-Type). Format of argument is:
1070     * $array['header-name'] = 'header-value';
1071     *
1072     * @param array $xtra_headers Assoc array with any extra headers (optional)
1073     *                            (Don't set Content-Type for multipart messages here!)
1074     * @param bool  $overwrite    Overwrite already existing headers.
1075     * @param bool  $skip_content Don't return content headers: Content-Type,
1076     *                            Content-Disposition and Content-Transfer-Encoding
1077     *
1078     * @return array              Assoc array with the mime headers
1079     * @access public
1080     */
1081    function &headers($xtra_headers = null, $overwrite = false, $skip_content = false)
1082    {
1083        // Add mime version header
1084        $headers['MIME-Version'] = '1.0';
1085
1086        // Content-Type and Content-Transfer-Encoding headers should already
1087        // be present if get() was called, but we'll re-set them to make sure
1088        // we got them when called before get() or something in the message
1089        // has been changed after get() [#14780]
1090        if (!$skip_content) {
1091            $headers += $this->_contentHeaders();
1092        }
1093
1094        if (!empty($xtra_headers)) {
1095            $headers = array_merge($headers, $xtra_headers);
1096        }
1097
1098        if ($overwrite) {
1099            $this->_headers = array_merge($this->_headers, $headers);
1100        } else {
1101            $this->_headers = array_merge($headers, $this->_headers);
1102        }
1103
1104        $headers = $this->_headers;
1105
1106        if ($skip_content) {
1107            unset($headers['Content-Type']);
1108            unset($headers['Content-Transfer-Encoding']);
1109            unset($headers['Content-Disposition']);
1110        } else if (!empty($this->_build_params['ctype'])) {
1111            $headers['Content-Type'] = $this->_build_params['ctype'];
1112        }
1113
1114        $encodedHeaders = $this->_encodeHeaders($headers);
1115        return $encodedHeaders;
1116    }
1117
1118    /**
1119     * Get the text version of the headers
1120     * (usefull if you want to use the PHP mail() function)
1121     *
1122     * @param array $xtra_headers Assoc array with any extra headers (optional)
1123     *                            (Don't set Content-Type for multipart messages here!)
1124     * @param bool  $overwrite    Overwrite the existing headers with new.
1125     * @param bool  $skip_content Don't return content headers: Content-Type,
1126     *                            Content-Disposition and Content-Transfer-Encoding
1127     *
1128     * @return string             Plain text headers
1129     * @access public
1130     */
1131    function txtHeaders($xtra_headers = null, $overwrite = false, $skip_content = false)
1132    {
1133        $headers = $this->headers($xtra_headers, $overwrite, $skip_content);
1134
1135        // Place Received: headers at the beginning of the message
1136        // Spam detectors often flag messages with it after the Subject: as spam
1137        if (isset($headers['Received'])) {
1138            $received = $headers['Received'];
1139            unset($headers['Received']);
1140            $headers = array('Received' => $received) + $headers;
1141        }
1142
1143        $ret = '';
1144        $eol = $this->_build_params['eol'];
1145
1146        foreach ($headers as $key => $val) {
1147            if (is_array($val)) {
1148                foreach ($val as $value) {
1149                    $ret .= "$key: $value" . $eol;
1150                }
1151            } else {
1152                $ret .= "$key: $val" . $eol;
1153            }
1154        }
1155
1156        return $ret;
1157    }
1158
1159    /**
1160     * Sets message Content-Type header.
1161     * Use it to build messages with various content-types e.g. miltipart/raport
1162     * not supported by _contentHeaders() function.
1163     *
1164     * @param string $type   Type name
1165     * @param array  $params Hash array of header parameters
1166     *
1167     * @return void
1168     * @access public
1169     * @since 1.7.0
1170     */
1171    function setContentType($type, $params = array())
1172    {
1173        $header = $type;
1174
1175        $eol = !empty($this->_build_params['eol'])
1176            ? $this->_build_params['eol'] : "\r\n";
1177
1178        // add parameters
1179        $token_regexp = '#([^\x21,\x23-\x27,\x2A,\x2B,\x2D'
1180            . ',\x2E,\x30-\x39,\x41-\x5A,\x5E-\x7E])#';
1181        if (is_array($params)) {
1182            foreach ($params as $name => $value) {
1183                if ($name == 'boundary') {
1184                    $this->_build_params['boundary'] = $value;
1185                }
1186                if (!preg_match($token_regexp, $value)) {
1187                    $header .= ";$eol $name=$value";
1188                } else {
1189                    $value = addcslashes($value, '\\"');
1190                    $header .= ";$eol $name=\"$value\"";
1191                }
1192            }
1193        }
1194
1195        // add required boundary parameter if not defined
1196        if (preg_match('/^multipart\//i', $type)) {
1197            if (empty($this->_build_params['boundary'])) {
1198                $this->_build_params['boundary'] = '=_' . md5(rand() . microtime());
1199            }
1200
1201            $header .= ";$eol boundary=\"".$this->_build_params['boundary']."\"";
1202        }
1203
1204        $this->_build_params['ctype'] = $header;
1205    }
1206
1207    /**
1208     * Sets the Subject header
1209     *
1210     * @param string $subject String to set the subject to.
1211     *
1212     * @return void
1213     * @access public
1214     */
1215    function setSubject($subject)
1216    {
1217        $this->_headers['Subject'] = $subject;
1218    }
1219
1220    /**
1221     * Set an email to the From (the sender) header
1222     *
1223     * @param string $email The email address to use
1224     *
1225     * @return void
1226     * @access public
1227     */
1228    function setFrom($email)
1229    {
1230        $this->_headers['From'] = $email;
1231    }
1232
1233    /**
1234     * Add an email to the To header
1235     * (multiple calls to this method are allowed)
1236     *
1237     * @param string $email The email direction to add
1238     *
1239     * @return void
1240     * @access public
1241     */
1242    function addTo($email)
1243    {
1244        if (isset($this->_headers['To'])) {
1245            $this->_headers['To'] .= ", $email";
1246        } else {
1247            $this->_headers['To'] = $email;
1248        }
1249    }
1250
1251    /**
1252     * Add an email to the Cc (carbon copy) header
1253     * (multiple calls to this method are allowed)
1254     *
1255     * @param string $email The email direction to add
1256     *
1257     * @return void
1258     * @access public
1259     */
1260    function addCc($email)
1261    {
1262        if (isset($this->_headers['Cc'])) {
1263            $this->_headers['Cc'] .= ", $email";
1264        } else {
1265            $this->_headers['Cc'] = $email;
1266        }
1267    }
1268
1269    /**
1270     * Add an email to the Bcc (blank carbon copy) header
1271     * (multiple calls to this method are allowed)
1272     *
1273     * @param string $email The email direction to add
1274     *
1275     * @return void
1276     * @access public
1277     */
1278    function addBcc($email)
1279    {
1280        if (isset($this->_headers['Bcc'])) {
1281            $this->_headers['Bcc'] .= ", $email";
1282        } else {
1283            $this->_headers['Bcc'] = $email;
1284        }
1285    }
1286
1287    /**
1288     * Since the PHP send function requires you to specify
1289     * recipients (To: header) separately from the other
1290     * headers, the To: header is not properly encoded.
1291     * To fix this, you can use this public method to
1292     * encode your recipients before sending to the send
1293     * function
1294     *
1295     * @param string $recipients A comma-delimited list of recipients
1296     *
1297     * @return string            Encoded data
1298     * @access public
1299     */
1300    function encodeRecipients($recipients)
1301    {
1302        $input = array("To" => $recipients);
1303        $retval = $this->_encodeHeaders($input);
1304        return $retval["To"] ;
1305    }
1306
1307    /**
1308     * Encodes headers as per RFC2047
1309     *
1310     * @param array $input  The header data to encode
1311     * @param array $params Extra build parameters
1312     *
1313     * @return array        Encoded data
1314     * @access private
1315     */
1316    function _encodeHeaders($input, $params = array())
1317    {
1318        $build_params = $this->_build_params;
1319        while (list($key, $value) = each($params)) {
1320            $build_params[$key] = $value;
1321        }
1322
1323        foreach ($input as $hdr_name => $hdr_value) {
1324            if (is_array($hdr_value)) {
1325                foreach ($hdr_value as $idx => $value) {
1326                    $input[$hdr_name][$idx] = $this->encodeHeader(
1327                        $hdr_name, $value,
1328                        $build_params['head_charset'], $build_params['head_encoding']
1329                    );
1330                }
1331            } else {
1332                $input[$hdr_name] = $this->encodeHeader(
1333                    $hdr_name, $hdr_value,
1334                    $build_params['head_charset'], $build_params['head_encoding']
1335                );
1336            }
1337        }
1338
1339        return $input;
1340    }
1341
1342    /**
1343     * Encodes a header as per RFC2047
1344     *
1345     * @param string $name     The header name
1346     * @param string $value    The header data to encode
1347     * @param string $charset  Character set name
1348     * @param string $encoding Encoding name (base64 or quoted-printable)
1349     *
1350     * @return string          Encoded header data (without a name)
1351     * @access public
1352     * @since 1.5.3
1353     */
1354    function encodeHeader($name, $value, $charset, $encoding)
1355    {
1356        return Mail_mimePart::encodeHeader(
1357            $name, $value, $charset, $encoding, $this->_build_params['eol']
1358        );
1359    }
1360
1361    /**
1362     * Get file's basename (locale independent)
1363     *
1364     * @param string $filename Filename
1365     *
1366     * @return string          Basename
1367     * @access private
1368     */
1369    function _basename($filename)
1370    {
1371        // basename() is not unicode safe and locale dependent
1372        if (stristr(PHP_OS, 'win') || stristr(PHP_OS, 'netware')) {
1373            return preg_replace('/^.*[\\\\\\/]/', '', $filename);
1374        } else {
1375            return preg_replace('/^.*[\/]/', '', $filename);
1376        }
1377    }
1378
1379    /**
1380     * Get Content-Type and Content-Transfer-Encoding headers of the message
1381     *
1382     * @return array Headers array
1383     * @access private
1384     */
1385    function _contentHeaders()
1386    {
1387        $attachments = count($this->_parts)                 ? true : false;
1388        $html_images = count($this->_html_images)           ? true : false;
1389        $html        = strlen($this->_htmlbody)             ? true : false;
1390        $text        = (!$html && strlen($this->_txtbody))  ? true : false;
1391        $headers     = array();
1392
1393        // See get()
1394        switch (true) {
1395        case $text && !$attachments:
1396            $headers['Content-Type'] = 'text/plain';
1397            break;
1398
1399        case !$text && !$html && $attachments:
1400        case $text && $attachments:
1401        case $html && $attachments && !$html_images:
1402        case $html && $attachments && $html_images:
1403            $headers['Content-Type'] = 'multipart/mixed';
1404            break;
1405
1406        case $html && !$attachments && !$html_images && isset($this->_txtbody):
1407        case $html && !$attachments && $html_images && isset($this->_txtbody):
1408            $headers['Content-Type'] = 'multipart/alternative';
1409            break;
1410
1411        case $html && !$attachments && !$html_images && !isset($this->_txtbody):
1412            $headers['Content-Type'] = 'text/html';
1413            break;
1414
1415        case $html && !$attachments && $html_images && !isset($this->_txtbody):
1416            $headers['Content-Type'] = 'multipart/related';
1417            break;
1418
1419        default:
1420            return $headers;
1421        }
1422
1423        $this->_checkParams();
1424
1425        $eol = !empty($this->_build_params['eol'])
1426            ? $this->_build_params['eol'] : "\r\n";
1427
1428        if ($headers['Content-Type'] == 'text/plain') {
1429            // single-part message: add charset and encoding
1430            $charset = 'charset=' . $this->_build_params['text_charset'];
1431            // place charset parameter in the same line, if possible
1432            // 26 = strlen("Content-Type: text/plain; ")
1433            $headers['Content-Type']
1434                .= (strlen($charset) + 26 <= 76) ? "; $charset" : ";$eol $charset";
1435            $headers['Content-Transfer-Encoding']
1436                = $this->_build_params['text_encoding'];
1437        } else if ($headers['Content-Type'] == 'text/html') {
1438            // single-part message: add charset and encoding
1439            $charset = 'charset=' . $this->_build_params['html_charset'];
1440            // place charset parameter in the same line, if possible
1441            $headers['Content-Type']
1442                .= (strlen($charset) + 25 <= 76) ? "; $charset" : ";$eol $charset";
1443            $headers['Content-Transfer-Encoding']
1444                = $this->_build_params['html_encoding'];
1445        } else {
1446            // multipart message: and boundary
1447            if (!empty($this->_build_params['boundary'])) {
1448                $boundary = $this->_build_params['boundary'];
1449            } else if (!empty($this->_headers['Content-Type'])
1450                && preg_match('/boundary="([^"]+)"/', $this->_headers['Content-Type'], $m)
1451            ) {
1452                $boundary = $m[1];
1453            } else {
1454                $boundary = '=_' . md5(rand() . microtime());
1455            }
1456
1457            $this->_build_params['boundary'] = $boundary;
1458            $headers['Content-Type'] .= ";$eol boundary=\"$boundary\"";
1459        }
1460
1461        return $headers;
1462    }
1463
1464    /**
1465     * Validate and set build parameters
1466     *
1467     * @return void
1468     * @access private
1469     */
1470    function _checkParams()
1471    {
1472        $encodings = array('7bit', '8bit', 'base64', 'quoted-printable');
1473
1474        $this->_build_params['text_encoding']
1475            = strtolower($this->_build_params['text_encoding']);
1476        $this->_build_params['html_encoding']
1477            = strtolower($this->_build_params['html_encoding']);
1478
1479        if (!in_array($this->_build_params['text_encoding'], $encodings)) {
1480            $this->_build_params['text_encoding'] = '7bit';
1481        }
1482        if (!in_array($this->_build_params['html_encoding'], $encodings)) {
1483            $this->_build_params['html_encoding'] = '7bit';
1484        }
1485
1486        // text body
1487        if ($this->_build_params['text_encoding'] == '7bit'
1488            && !preg_match('/ascii/i', $this->_build_params['text_charset'])
1489            && preg_match('/[^\x00-\x7F]/', $this->_txtbody)
1490        ) {
1491            $this->_build_params['text_encoding'] = 'quoted-printable';
1492        }
1493        // html body
1494        if ($this->_build_params['html_encoding'] == '7bit'
1495            && !preg_match('/ascii/i', $this->_build_params['html_charset'])
1496            && preg_match('/[^\x00-\x7F]/', $this->_htmlbody)
1497        ) {
1498            $this->_build_params['html_encoding'] = 'quoted-printable';
1499        }
1500    }
1501
1502} // End of class
Note: See TracBrowser for help on using the repository browser.