mime.php 23 KB

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