PageRenderTime 51ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/program/lib/Mail/mime.php

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