Automatically exported from code.google.com/p/planningalerts
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

714 lines
24 KiB

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