"Fossies" - the Fresh Open Source Software Archive 
As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) PHP source code syntax highlighting (style:
standard) with prefixed line numbers and
code folding option.
Alternatively you can here
view or
download the uninterpreted source code file.
For more information about "mime.php" see the
Fossies "Dox" file reference documentation.
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 297878 2010-04-12 10:57:34Z 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 */
66 // require_once '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 '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 */
95 class 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' => 'base64',
150 // What encoding to use for html
151 // Options: 7bit, 8bit, base64, or quoted-printable
152 'html_encoding' => 'base64',
153 // The character set to use for html
154 'html_charset' => 'utf-8',
155 // The character set to use for text
156 'text_charset' => 'utf-8',
157 // The character set to use for headers
158 'head_charset' => 'utf-8',
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 ($cont === false) {
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 ($cont === false) {
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 (!($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
373 * Defaults to true
374 * @param string $encoding The type of encoding to use.
375 * Defaults to base64.
376 * Possible values: 7bit, 8bit, base64,
377 * or quoted-printable.
378 * @param string $disposition The content-disposition of this file
379 * Defaults to attachment.
380 * Possible values: attachment, inline.
381 * @param string $charset The character set used in the filename
382 * of this attachment.
383 * @param string $language The language of the attachment
384 * @param string $location The RFC 2557.4 location of the attachment
385 * @param string $n_encoding Encoding for attachment name (Content-Type)
386 * By default filenames are encoded using RFC2231 method
387 * Here you can set RFC2047 encoding (quoted-printable
388 * or base64) instead
389 * @param string $f_encoding Encoding for attachment filename (Content-Disposition)
390 * See $n_encoding description
391 * @param string $description Content-Description header
392 *
393 * @return mixed True on success or PEAR_Error object
394 * @access public
395 */
396 function addAttachment($file,
397 $c_type = 'application/octet-stream',
398 $name = '',
399 $isfile = true,
400 $encoding = 'base64',
401 $disposition = 'attachment',
402 $charset = '',
403 $language = '',
404 $location = '',
405 $n_encoding = null,
406 $f_encoding = null,
407 $description = ''
408 ) {
409 $bodyfile = null;
410
411 if ($isfile) {
412 // Don't load file into memory
413 if ($this->_build_params['delay_file_io']) {
414 $filedata = null;
415 $bodyfile = $file;
416 } else {
417 if (!($filedata = $this->_file2str($file))) {
418 return $filedata;
419 }
420 }
421 // Force the name the user supplied, otherwise use $file
422 $filename = ($name ? $name : $file);
423 } else {
424 $filedata = $file;
425 $filename = $name;
426 }
427
428 if (!strlen($filename)) {
429 $msg = "The supplied filename for the attachment can't be empty";
430 debugLog($msg);
431 return false;
432 }
433 $filename = $this->_basename($filename);
434
435 $this->_parts[] = array(
436 'body' => $filedata,
437 'body_file' => $bodyfile,
438 'name' => $filename,
439 'c_type' => $c_type,
440 'encoding' => $encoding,
441 'charset' => $charset,
442 'language' => $language,
443 'location' => $location,
444 'disposition' => $disposition,
445 'description' => $description,
446 'name_encoding' => $n_encoding,
447 'filename_encoding' => $f_encoding
448 );
449
450 return true;
451 }
452
453 /**
454 * Get the contents of the given file name as string
455 *
456 * @param string $file_name Path of file to process
457 *
458 * @return string Contents of $file_name
459 * @access private
460 */
461 function &_file2str($file_name)
462 {
463 // Check state of file and raise an error properly
464 if (!file_exists($file_name)) {
465 debugLog('File not found: ' . $file_name);
466 return false;
467 }
468 if (!is_file($file_name)) {
469 debugLog('Not a regular file: ' . $file_name);
470 return false;
471 }
472 if (!is_readable($file_name)) {
473 debugLog('File is not readable: ' . $file_name);
474 return false;
475 }
476
477 // Temporarily reset magic_quotes_runtime and read file contents
478 if ($magic_quote_setting = get_magic_quotes_runtime()) {
479 @ini_set('magic_quotes_runtime', 0);
480 }
481 $cont = file_get_contents($file_name);
482 if ($magic_quote_setting) {
483 @ini_set('magic_quotes_runtime', $magic_quote_setting);
484 }
485
486 return $cont;
487 }
488
489 /**
490 * Adds a text subpart to the mimePart object and
491 * returns it during the build process.
492 *
493 * @param mixed &$obj The object to add the part to, or
494 * null if a new object is to be created.
495 * @param string $text The text to add.
496 *
497 * @return object The text mimePart object
498 * @access private
499 */
500 function &_addTextPart(&$obj, $text)
501 {
502 $params['content_type'] = 'text/plain';
503 $params['encoding'] = $this->_build_params['text_encoding'];
504 $params['charset'] = $this->_build_params['text_charset'];
505 $params['eol'] = $this->_build_params['eol'];
506
507 if (is_object($obj)) {
508 $ret = $obj->addSubpart($text, $params);
509 return $ret;
510 } else {
511 $ret = new Mail_mimePart($text, $params);
512 return $ret;
513 }
514 }
515
516 /**
517 * Adds a html subpart to the mimePart object and
518 * returns it during the build process.
519 *
520 * @param mixed &$obj The object to add the part to, or
521 * null if a new object is to be created.
522 *
523 * @return object The html mimePart object
524 * @access private
525 */
526 function &_addHtmlPart(&$obj)
527 {
528 $params['content_type'] = 'text/html';
529 $params['encoding'] = $this->_build_params['html_encoding'];
530 $params['charset'] = $this->_build_params['html_charset'];
531 $params['eol'] = $this->_build_params['eol'];
532
533 if (is_object($obj)) {
534 $ret = $obj->addSubpart($this->_htmlbody, $params);
535 return $ret;
536 } else {
537 $ret = new Mail_mimePart($this->_htmlbody, $params);
538 return $ret;
539 }
540 }
541
542 /**
543 * Creates a new mimePart object, using multipart/mixed as
544 * the initial content-type and returns it during the
545 * build process.
546 *
547 * @return object The multipart/mixed mimePart object
548 * @access private
549 */
550 function &_addMixedPart()
551 {
552 $params = array();
553 $params['content_type'] = 'multipart/mixed';
554 $params['eol'] = $this->_build_params['eol'];
555
556 // Create empty multipart/mixed Mail_mimePart object to return
557 $ret = new Mail_mimePart('', $params);
558 return $ret;
559 }
560
561 /**
562 * Adds a multipart/alternative part to a mimePart
563 * object (or creates one), and returns it during
564 * the build process.
565 *
566 * @param mixed &$obj The object to add the part to, or
567 * null if a new object is to be created.
568 *
569 * @return object The multipart/mixed mimePart object
570 * @access private
571 */
572 function &_addAlternativePart(&$obj)
573 {
574 $params['content_type'] = 'multipart/alternative';
575 $params['eol'] = $this->_build_params['eol'];
576
577 if (is_object($obj)) {
578 return $obj->addSubpart('', $params);
579 } else {
580 $ret = new Mail_mimePart('', $params);
581 return $ret;
582 }
583 }
584
585 /**
586 * Adds a multipart/related part to a mimePart
587 * object (or creates one), and returns it during
588 * the build process.
589 *
590 * @param mixed &$obj The object to add the part to, or
591 * null if a new object is to be created
592 *
593 * @return object The multipart/mixed mimePart object
594 * @access private
595 */
596 function &_addRelatedPart(&$obj)
597 {
598 $params['content_type'] = 'multipart/related';
599 $params['eol'] = $this->_build_params['eol'];
600
601 if (is_object($obj)) {
602 return $obj->addSubpart('', $params);
603 } else {
604 $ret = new Mail_mimePart('', $params);
605 return $ret;
606 }
607 }
608
609 /**
610 * Adds an html image subpart to a mimePart object
611 * and returns it during the build process.
612 *
613 * @param object &$obj The mimePart to add the image to
614 * @param array $value The image information
615 *
616 * @return object The image mimePart object
617 * @access private
618 */
619 function &_addHtmlImagePart(&$obj, $value)
620 {
621 $params['content_type'] = $value['c_type'];
622 $params['encoding'] = 'base64';
623 $params['disposition'] = 'inline';
624 $params['dfilename'] = $value['name'];
625 $params['cid'] = $value['cid'];
626 $params['body_file'] = $value['body_file'];
627 $params['eol'] = $this->_build_params['eol'];
628
629 if (!empty($value['name_encoding'])) {
630 $params['name_encoding'] = $value['name_encoding'];
631 }
632 if (!empty($value['filename_encoding'])) {
633 $params['filename_encoding'] = $value['filename_encoding'];
634 }
635
636 $ret = $obj->addSubpart($value['body'], $params);
637 return $ret;
638 }
639
640 /**
641 * Adds an attachment subpart to a mimePart object
642 * and returns it during the build process.
643 *
644 * @param object &$obj The mimePart to add the image to
645 * @param array $value The attachment information
646 *
647 * @return object The image mimePart object
648 * @access private
649 */
650 function &_addAttachmentPart(&$obj, $value)
651 {
652 $params['eol'] = $this->_build_params['eol'];
653 $params['dfilename'] = $value['name'];
654 $params['encoding'] = $value['encoding'];
655 $params['content_type'] = $value['c_type'];
656 $params['body_file'] = $value['body_file'];
657 $params['disposition'] = isset($value['disposition']) ?
658 $value['disposition'] : 'attachment';
659 if ($value['charset']) {
660 $params['charset'] = $value['charset'];
661 }
662 if ($value['language']) {
663 $params['language'] = $value['language'];
664 }
665 if ($value['location']) {
666 $params['location'] = $value['location'];
667 }
668 if (!empty($value['name_encoding'])) {
669 $params['name_encoding'] = $value['name_encoding'];
670 }
671 if (!empty($value['filename_encoding'])) {
672 $params['filename_encoding'] = $value['filename_encoding'];
673 }
674 if (!empty($value['description'])) {
675 $params['description'] = $value['description'];
676 }
677
678 $ret = $obj->addSubpart($value['body'], $params);
679 return $ret;
680 }
681
682 /**
683 * Returns the complete e-mail, ready to send using an alternative
684 * mail delivery method. Note that only the mailpart that is made
685 * with Mail_Mime is created. This means that,
686 * YOU WILL HAVE NO TO: HEADERS UNLESS YOU SET IT YOURSELF
687 * using the $headers parameter!
688 *
689 * @param string $separation The separation between these two parts.
690 * @param array $params The Build parameters passed to the
691 * &get() function. See &get for more info.
692 * @param array $headers The extra headers that should be passed
693 * to the &headers() function.
694 * See that function for more info.
695 * @param bool $overwrite Overwrite the existing headers with new.
696 *
697 * @return mixed The complete e-mail or PEAR error object
698 * @access public
699 */
700 function getMessage($separation = null, $params = null, $headers = null,
701 $overwrite = false
702 ) {
703 if ($separation === null) {
704 $separation = $this->_build_params['eol'];
705 }
706
707 $body = $this->get($params);
708
709 if ($body === false) {
710 return $body;
711 }
712
713 $head = $this->txtHeaders($headers, $overwrite);
714 $mail = $head . $separation . $body;
715 return $mail;
716 }
717
718 /**
719 * Returns the complete e-mail body, ready to send using an alternative
720 * mail delivery method.
721 *
722 * @param array $params The Build parameters passed to the
723 * &get() function. See &get for more info.
724 *
725 * @return mixed The e-mail body or PEAR error object
726 * @access public
727 * @since 1.6.0
728 */
729 function getMessageBody($params = null)
730 {
731 return $this->get($params, null, true);
732 }
733
734 /**
735 * Writes (appends) the complete e-mail into file.
736 *
737 * @param string $filename Output file location
738 * @param array $params The Build parameters passed to the
739 * &get() function. See &get for more info.
740 * @param array $headers The extra headers that should be passed
741 * to the &headers() function.
742 * See that function for more info.
743 * @param bool $overwrite Overwrite the existing headers with new.
744 *
745 * @return mixed True or PEAR error object
746 * @access public
747 * @since 1.6.0
748 */
749 function saveMessage($filename, $params = null, $headers = null, $overwrite = false)
750 {
751 // Check state of file and raise an error properly
752 if (file_exists($filename) && !is_writable($filename)) {
753 debugLog('File is not writable: ' . $filename);
754 return false;
755 }
756
757 // Temporarily reset magic_quotes_runtime and read file contents
758 if ($magic_quote_setting = get_magic_quotes_runtime()) {
759 @ini_set('magic_quotes_runtime', 0);
760 }
761
762 if (!($fh = fopen($filename, 'ab'))) {
763 debugLog('Unable to open file: ' . $filename);
764 return false;
765 }
766
767 // Write message headers into file (skipping Content-* headers)
768 $head = $this->txtHeaders($headers, $overwrite, true);
769 if (fwrite($fh, $head) === false) {
770 debugLog('Error writing to file: ' . $filename);
771 return false;
772 }
773
774 fclose($fh);
775
776 if ($magic_quote_setting) {
777 @ini_set('magic_quotes_runtime', $magic_quote_setting);
778 }
779
780 // Write the rest of the message into file
781 $res = $this->get($params, $filename);
782
783 return $res ? $res : true;
784 }
785
786 /**
787 * Writes (appends) the complete e-mail body into file.
788 *
789 * @param string $filename Output file location
790 * @param array $params The Build parameters passed to the
791 * &get() function. See &get for more info.
792 *
793 * @return mixed True or PEAR error object
794 * @access public
795 * @since 1.6.0
796 */
797 function saveMessageBody($filename, $params = null)
798 {
799 // Check state of file and raise an error properly
800 if (file_exists($filename) && !is_writable($filename)) {
801 debugLog('File is not writable: ' . $filename);
802 return false;
803 }
804
805 // Temporarily reset magic_quotes_runtime and read file contents
806 if ($magic_quote_setting = get_magic_quotes_runtime()) {
807 @ini_set('magic_quotes_runtime', 0);
808 }
809
810 if (!($fh = fopen($filename, 'ab'))) {
811 debugLog('Unable to open file: ' . $filename);
812 return false;
813 }
814
815 // Write the rest of the message into file
816 $res = $this->get($params, $filename, true);
817
818 return $res ? $res : true;
819 }
820
821 /**
822 * Builds the multipart message from the list ($this->_parts) and
823 * returns the mime content.
824 *
825 * @param array $params Build parameters that change the way the email
826 * is built. Should be associative. See $_build_params.
827 * @param resource $filename Output file where to save the message instead of
828 * returning it
829 * @param boolean $skip_head True if you want to return/save only the message
830 * without headers
831 *
832 * @return mixed The MIME message content string, null or PEAR error object
833 * @access public
834 */
835 function &get($params = null, $filename = null, $skip_head = false)
836 {
837 if (isset($params)) {
838 while (list($key, $value) = each($params)) {
839 $this->_build_params[$key] = $value;
840 }
841 }
842
843 if (isset($this->_headers['From'])) {
844 // Bug #11381: Illegal characters in domain ID
845 if (preg_match("|(@[0-9a-zA-Z\-\.]+)|", $this->_headers['From'], $matches)) {
846 $domainID = $matches[1];
847 } else {
848 $domainID = "@localhost";
849 }
850 foreach ($this->_html_images as $i => $img) {
851 $this->_html_images[$i]['cid']
852 = $this->_html_images[$i]['cid'] . $domainID;
853 }
854 }
855
856 if (count($this->_html_images) && isset($this->_htmlbody)) {
857 foreach ($this->_html_images as $key => $value) {
858 $regex = array();
859 $regex[] = '#(\s)((?i)src|background|href(?-i))\s*=\s*(["\']?)' .
860 preg_quote($value['name'], '#') . '\3#';
861 $regex[] = '#(?i)url(?-i)\(\s*(["\']?)' .
862 preg_quote($value['name'], '#') . '\1\s*\)#';
863
864 $rep = array();
865 $rep[] = '\1\2=\3cid:' . $value['cid'] .'\3';
866 $rep[] = 'url(\1cid:' . $value['cid'] . '\1)';
867
868 $this->_htmlbody = preg_replace($regex, $rep, $this->_htmlbody);
869 $this->_html_images[$key]['name']
870 = $this->_basename($this->_html_images[$key]['name']);
871 }
872 }
873
874 $this->_checkParams();
875
876 $null = null;
877 $attachments = count($this->_parts) ? true : false;
878 $html_images = count($this->_html_images) ? true : false;
879 $html = strlen($this->_htmlbody) ? true : false;
880 $text = (!$html && strlen($this->_txtbody)) ? true : false;
881
882 switch (true) {
883 case $text && !$attachments:
884 $message =& $this->_addTextPart($null, $this->_txtbody);
885 break;
886
887 case !$text && !$html && $attachments:
888 $message =& $this->_addMixedPart();
889 for ($i = 0; $i < count($this->_parts); $i++) {
890 $this->_addAttachmentPart($message, $this->_parts[$i]);
891 }
892 break;
893
894 case $text && $attachments:
895 $message =& $this->_addMixedPart();
896 $this->_addTextPart($message, $this->_txtbody);
897 for ($i = 0; $i < count($this->_parts); $i++) {
898 $this->_addAttachmentPart($message, $this->_parts[$i]);
899 }
900 break;
901
902 case $html && !$attachments && !$html_images:
903 if (isset($this->_txtbody)) {
904 $message =& $this->_addAlternativePart($null);
905 $this->_addTextPart($message, $this->_txtbody);
906 $this->_addHtmlPart($message);
907 } else {
908 $message =& $this->_addHtmlPart($null);
909 }
910 break;
911
912 case $html && !$attachments && $html_images:
913 // * Content-Type: multipart/alternative;
914 // * text
915 // * Content-Type: multipart/related;
916 // * html
917 // * image...
918 if (isset($this->_txtbody)) {
919 $message =& $this->_addAlternativePart($null);
920 $this->_addTextPart($message, $this->_txtbody);
921
922 $ht =& $this->_addRelatedPart($message);
923 $this->_addHtmlPart($ht);
924 for ($i = 0; $i < count($this->_html_images); $i++) {
925 $this->_addHtmlImagePart($ht, $this->_html_images[$i]);
926 }
927 } else {
928 // * Content-Type: multipart/related;
929 // * html
930 // * image...
931 $message =& $this->_addRelatedPart($null);
932 $this->_addHtmlPart($message);
933 for ($i = 0; $i < count($this->_html_images); $i++) {
934 $this->_addHtmlImagePart($message, $this->_html_images[$i]);
935 }
936 }
937 /*
938 // #13444, #9725: the code below was a non-RFC compliant hack
939 // * Content-Type: multipart/related;
940 // * Content-Type: multipart/alternative;
941 // * text
942 // * html
943 // * image...
944 $message =& $this->_addRelatedPart($null);
945 if (isset($this->_txtbody)) {
946 $alt =& $this->_addAlternativePart($message);
947 $this->_addTextPart($alt, $this->_txtbody);
948 $this->_addHtmlPart($alt);
949 } else {
950 $this->_addHtmlPart($message);
951 }
952 for ($i = 0; $i < count($this->_html_images); $i++) {
953 $this->_addHtmlImagePart($message, $this->_html_images[$i]);
954 }
955 */
956 break;
957
958 case $html && $attachments && !$html_images:
959 $message =& $this->_addMixedPart();
960 if (isset($this->_txtbody)) {
961 $alt =& $this->_addAlternativePart($message);
962 $this->_addTextPart($alt, $this->_txtbody);
963 $this->_addHtmlPart($alt);
964 } else {
965 $this->_addHtmlPart($message);
966 }
967 for ($i = 0; $i < count($this->_parts); $i++) {
968 $this->_addAttachmentPart($message, $this->_parts[$i]);
969 }
970 break;
971
972 case $html && $attachments && $html_images:
973 $message =& $this->_addMixedPart();
974 if (isset($this->_txtbody)) {
975 $alt =& $this->_addAlternativePart($message);
976 $this->_addTextPart($alt, $this->_txtbody);
977 $rel =& $this->_addRelatedPart($alt);
978 } else {
979 $rel =& $this->_addRelatedPart($message);
980 }
981 $this->_addHtmlPart($rel);
982 for ($i = 0; $i < count($this->_html_images); $i++) {
983 $this->_addHtmlImagePart($rel, $this->_html_images[$i]);
984 }
985 for ($i = 0; $i < count($this->_parts); $i++) {
986 $this->_addAttachmentPart($message, $this->_parts[$i]);
987 }
988 break;
989
990 }
991
992 if (!isset($message)) {
993 $ret = null;
994 return $ret;
995 }
996
997 // Use saved boundary
998 if (!empty($this->_build_params['boundary'])) {
999 $boundary = $this->_build_params['boundary'];
1000 } else {
1001 $boundary = null;
1002 }
1003
1004 // Write output to file
1005 if ($filename) {
1006 // Append mimePart message headers and body into file
1007 $headers = $message->encodeToFile($filename, $boundary, $skip_head);
1008 if ($headers === false) {
1009 return $headers;
1010 }
1011 $this->_headers = array_merge($this->_headers, $headers);
1012 $ret = null;
1013 return $ret;
1014 } else {
1015 $output = $message->encode($boundary, $skip_head);
1016 if ($output === false) {
1017 return false;
1018 }
1019 $this->_headers = array_merge($this->_headers, $output['headers']);
1020 $body = $output['body'];
1021 return $body;
1022 }
1023 }
1024
1025 /**
1026 * Returns an array with the headers needed to prepend to the email
1027 * (MIME-Version and Content-Type). Format of argument is:
1028 * $array['header-name'] = 'header-value';
1029 *
1030 * @param array $xtra_headers Assoc array with any extra headers (optional)
1031 * (Don't set Content-Type for multipart messages here!)
1032 * @param bool $overwrite Overwrite already existing headers.
1033 * @param bool $skip_content Don't return content headers: Content-Type,
1034 * Content-Disposition and Content-Transfer-Encoding
1035 *
1036 * @return array Assoc array with the mime headers
1037 * @access public
1038 */
1039 function &headers($xtra_headers = null, $overwrite = false, $skip_content = false)
1040 {
1041 // Add mime version header
1042 $headers['MIME-Version'] = '1.0';
1043
1044 // Content-Type and Content-Transfer-Encoding headers should already
1045 // be present if get() was called, but we'll re-set them to make sure
1046 // we got them when called before get() or something in the message
1047 // has been changed after get() [#14780]
1048 if (!$skip_content) {
1049 $headers += $this->_contentHeaders();
1050 }
1051
1052 if (!empty($xtra_headers)) {
1053 $headers = array_merge($headers, $xtra_headers);
1054 }
1055
1056 if ($overwrite) {
1057 $this->_headers = array_merge($this->_headers, $headers);
1058 } else {
1059 $this->_headers = array_merge($headers, $this->_headers);
1060 }
1061
1062 $headers = $this->_headers;
1063
1064 if ($skip_content) {
1065 unset($headers['Content-Type']);
1066 unset($headers['Content-Transfer-Encoding']);
1067 unset($headers['Content-Disposition']);
1068 } else if (!empty($this->_build_params['ctype'])) {
1069 $headers['Content-Type'] = $this->_build_params['ctype'];
1070 }
1071
1072 $encodedHeaders = $this->_encodeHeaders($headers);
1073 return $encodedHeaders;
1074 }
1075
1076 /**
1077 * Get the text version of the headers
1078 * (usefull if you want to use the PHP mail() function)
1079 *
1080 * @param array $xtra_headers Assoc array with any extra headers (optional)
1081 * (Don't set Content-Type for multipart messages here!)
1082 * @param bool $overwrite Overwrite the existing headers with new.
1083 * @param bool $skip_content Don't return content headers: Content-Type,
1084 * Content-Disposition and Content-Transfer-Encoding
1085 *
1086 * @return string Plain text headers
1087 * @access public
1088 */
1089 function txtHeaders($xtra_headers = null, $overwrite = false, $skip_content = false)
1090 {
1091 $headers = $this->headers($xtra_headers, $overwrite, $skip_content);
1092
1093 // Place Received: headers at the beginning of the message
1094 // Spam detectors often flag messages with it after the Subject: as spam
1095 if (isset($headers['Received'])) {
1096 $received = $headers['Received'];
1097 unset($headers['Received']);
1098 $headers = array('Received' => $received) + $headers;
1099 }
1100
1101 $ret = '';
1102 $eol = $this->_build_params['eol'];
1103
1104 foreach ($headers as $key => $val) {
1105 if (is_array($val)) {
1106 foreach ($val as $value) {
1107 $ret .= "$key: $value" . $eol;
1108 }
1109 } else {
1110 $ret .= "$key: $val" . $eol;
1111 }
1112 }
1113
1114 return $ret;
1115 }
1116
1117 /**
1118 * Sets message Content-Type header.
1119 * Use it to build messages with various content-types e.g. miltipart/raport
1120 * not supported by _contentHeaders() function.
1121 *
1122 * @param string $type Type name
1123 * @param array $params Hash array of header parameters
1124 *
1125 * @return void
1126 * @access public
1127 * @since 1.7.0
1128 */
1129 function setContentType($type, $params = array())
1130 {
1131 $header = $type;
1132
1133 $eol = !empty($this->_build_params['eol'])
1134 ? $this->_build_params['eol'] : "\r\n";
1135
1136 // add parameters
1137 $token_regexp = '#([^\x21,\x23-\x27,\x2A,\x2B,\x2D'
1138 . ',\x2E,\x30-\x39,\x41-\x5A,\x5E-\x7E])#';
1139 if (is_array($params)) {
1140 foreach ($params as $name => $value) {
1141 if ($name == 'boundary') {
1142 $this->_build_params['boundary'] = $value;
1143 }
1144 if (!preg_match($token_regexp, $value)) {
1145 $header .= ";$eol $name=$value";
1146 } else {
1147 $value = addcslashes($value, '\\"');
1148 $header .= ";$eol $name=\"$value\"";
1149 }
1150 }
1151 }
1152
1153 // add required boundary parameter if not defined
1154 if (preg_match('/^multipart\//i', $type)) {
1155 if (empty($this->_build_params['boundary'])) {
1156 $this->_build_params['boundary'] = '=_' . md5(rand() . microtime());
1157 }
1158
1159 $header .= ";$eol boundary=\"".$this->_build_params['boundary']."\"";
1160 }
1161
1162 $this->_build_params['ctype'] = $header;
1163 }
1164
1165 /**
1166 * Sets the Subject header
1167 *
1168 * @param string $subject String to set the subject to.
1169 *
1170 * @return void
1171 * @access public
1172 */
1173 function setSubject($subject)
1174 {
1175 $this->_headers['Subject'] = $subject;
1176 }
1177
1178 /**
1179 * Set an email to the From (the sender) header
1180 *
1181 * @param string $email The email address to use
1182 *
1183 * @return void
1184 * @access public
1185 */
1186 function setFrom($email)
1187 {
1188 $this->_headers['From'] = $email;
1189 }
1190
1191 /**
1192 * Add an email to the Cc (carbon copy) header
1193 * (multiple calls to this method are allowed)
1194 *
1195 * @param string $email The email direction to add
1196 *
1197 * @return void
1198 * @access public
1199 */
1200 function addCc($email)
1201 {
1202 if (isset($this->_headers['Cc'])) {
1203 $this->_headers['Cc'] .= ", $email";
1204 } else {
1205 $this->_headers['Cc'] = $email;
1206 }
1207 }
1208
1209 /**
1210 * Add an email to the Bcc (blank carbon copy) header
1211 * (multiple calls to this method are allowed)
1212 *
1213 * @param string $email The email direction to add
1214 *
1215 * @return void
1216 * @access public
1217 */
1218 function addBcc($email)
1219 {
1220 if (isset($this->_headers['Bcc'])) {
1221 $this->_headers['Bcc'] .= ", $email";
1222 } else {
1223 $this->_headers['Bcc'] = $email;
1224 }
1225 }
1226
1227 /**
1228 * Since the PHP send function requires you to specify
1229 * recipients (To: header) separately from the other
1230 * headers, the To: header is not properly encoded.
1231 * To fix this, you can use this public method to
1232 * encode your recipients before sending to the send
1233 * function
1234 *
1235 * @param string $recipients A comma-delimited list of recipients
1236 *
1237 * @return string Encoded data
1238 * @access public
1239 */
1240 function encodeRecipients($recipients)
1241 {
1242 $input = array("To" => $recipients);
1243 $retval = $this->_encodeHeaders($input);
1244 return $retval["To"] ;
1245 }
1246
1247 /**
1248 * Encodes headers as per RFC2047
1249 *
1250 * @param array $input The header data to encode
1251 * @param array $params Extra build parameters
1252 *
1253 * @return array Encoded data
1254 * @access private
1255 */
1256 function _encodeHeaders($input, $params = array())
1257 {
1258 $build_params = $this->_build_params;
1259 while (list($key, $value) = each($params)) {
1260 $build_params[$key] = $value;
1261 }
1262
1263 foreach ($input as $hdr_name => $hdr_value) {
1264 if (is_array($hdr_value)) {
1265 foreach ($hdr_value as $idx => $value) {
1266 $input[$hdr_name][$idx] = $this->encodeHeader(
1267 $hdr_name, $value,
1268 $build_params['head_charset'], $build_params['head_encoding']
1269 );
1270 }
1271 } else {
1272 $input[$hdr_name] = $this->encodeHeader(
1273 $hdr_name, $hdr_value,
1274 $build_params['head_charset'], $build_params['head_encoding']
1275 );
1276 }
1277 }
1278
1279 return $input;
1280 }
1281
1282 /**
1283 * Encodes a header as per RFC2047
1284 *
1285 * @param string $name The header name
1286 * @param string $value The header data to encode
1287 * @param string $charset Character set name
1288 * @param string $encoding Encoding name (base64 or quoted-printable)
1289 *
1290 * @return string Encoded header data (without a name)
1291 * @access public
1292 * @since 1.5.3
1293 */
1294 function encodeHeader($name, $value, $charset, $encoding)
1295 {
1296 return Mail_mimePart::encodeHeader(
1297 $name, $value, $charset, $encoding, $this->_build_params['eol']
1298 );
1299 }
1300
1301 /**
1302 * Get file's basename (locale independent)
1303 *
1304 * @param string $filename Filename
1305 *
1306 * @return string Basename
1307 * @access private
1308 */
1309 function _basename($filename)
1310 {
1311 // basename() is not unicode safe and locale dependent
1312 if (stristr(PHP_OS, 'win') || stristr(PHP_OS, 'netware')) {
1313 return preg_replace('/^.*[\\\\\\/]/', '', $filename);
1314 } else {
1315 return preg_replace('/^.*[\/]/', '', $filename);
1316 }
1317 }
1318
1319 /**
1320 * Get Content-Type and Content-Transfer-Encoding headers of the message
1321 *
1322 * @return array Headers array
1323 * @access private
1324 */
1325 function _contentHeaders()
1326 {
1327 $attachments = count($this->_parts) ? true : false;
1328 $html_images = count($this->_html_images) ? true : false;
1329 $html = strlen($this->_htmlbody) ? true : false;
1330 $text = (!$html && strlen($this->_txtbody)) ? true : false;
1331 $headers = array();
1332
1333 // See get()
1334 switch (true) {
1335 case $text && !$attachments:
1336 $headers['Content-Type'] = 'text/plain';
1337 break;
1338
1339 case !$text && !$html && $attachments:
1340 case $text && $attachments:
1341 case $html && $attachments && !$html_images:
1342 case $html && $attachments && $html_images:
1343 $headers['Content-Type'] = 'multipart/mixed';
1344 break;
1345
1346 case $html && !$attachments && !$html_images && isset($this->_txtbody):
1347 case $html && !$attachments && $html_images && isset($this->_txtbody):
1348 $headers['Content-Type'] = 'multipart/alternative';
1349 break;
1350
1351 case $html && !$attachments && !$html_images && !isset($this->_txtbody):
1352 $headers['Content-Type'] = 'text/html';
1353 break;
1354
1355 case $html && !$attachments && $html_images && !isset($this->_txtbody):
1356 $headers['Content-Type'] = 'multipart/related';
1357 break;
1358
1359 default:
1360 return $headers;
1361 }
1362
1363 $this->_checkParams();
1364
1365 $eol = !empty($this->_build_params['eol'])
1366 ? $this->_build_params['eol'] : "\r\n";
1367
1368 if ($headers['Content-Type'] == 'text/plain') {
1369 // single-part message: add charset and encoding
1370 $headers['Content-Type']
1371 .= ";$eol charset=" . $this->_build_params['text_charset'];
1372 $headers['Content-Transfer-Encoding']
1373 = $this->_build_params['text_encoding'];
1374 } else if ($headers['Content-Type'] == 'text/html') {
1375 // single-part message: add charset and encoding
1376 $headers['Content-Type']
1377 .= ";$eol charset=" . $this->_build_params['html_charset'];
1378 $headers['Content-Transfer-Encoding']
1379 = $this->_build_params['html_encoding'];
1380 } else {
1381 // multipart message: add charset and boundary
1382 if (!empty($this->_build_params['boundary'])) {
1383 $boundary = $this->_build_params['boundary'];
1384 } else if (!empty($this->_headers['Content-Type'])
1385 && preg_match('/boundary="([^"]+)"/', $this->_headers['Content-Type'], $m)
1386 ) {
1387 $boundary = $m[1];
1388 } else {
1389 $boundary = '=_' . md5(rand() . microtime());
1390 }
1391
1392 $this->_build_params['boundary'] = $boundary;
1393 $headers['Content-Type'] .= ";$eol boundary=\"$boundary\"";
1394 }
1395
1396 return $headers;
1397 }
1398
1399 /**
1400 * Validate and set build parameters
1401 *
1402 * @return void
1403 * @access private
1404 */
1405 function _checkParams()
1406 {
1407 $encodings = array('7bit', '8bit', 'base64', 'quoted-printable');
1408
1409 $this->_build_params['text_encoding']
1410 = strtolower($this->_build_params['text_encoding']);
1411 $this->_build_params['html_encoding']
1412 = strtolower($this->_build_params['html_encoding']);
1413
1414 if (!in_array($this->_build_params['text_encoding'], $encodings)) {
1415 $this->_build_params['text_encoding'] = '7bit';
1416 }
1417 if (!in_array($this->_build_params['html_encoding'], $encodings)) {
1418 $this->_build_params['html_encoding'] = '7bit';
1419 }
1420
1421 // text body
1422 if ($this->_build_params['text_encoding'] == '7bit'
1423 && !preg_match('/ascii/i', $this->_build_params['text_charset'])
1424 && preg_match('/[^\x00-\x7F]/', $this->_txtbody)
1425 ) {
1426 $this->_build_params['text_encoding'] = 'quoted-printable';
1427 }
1428 // html body
1429 if ($this->_build_params['html_encoding'] == '7bit'
1430 && !preg_match('/ascii/i', $this->_build_params['html_charset'])
1431 && preg_match('/[^\x00-\x7F]/', $this->_htmlbody)
1432 ) {
1433 $this->_build_params['html_encoding'] = 'quoted-printable';
1434 }
1435 }
1436
1437 } // End of class