PageRenderTime 55ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/code/ryzom/tools/server/www/login/email/htmlMimeMail.php

https://bitbucket.org/SirCotare/ryzom
PHP | 777 lines | 451 code | 94 blank | 232 comment | 68 complexity | ea8deb1c4ca1affe5f571ac7f876973e MD5 | raw file
Possible License(s): AGPL-3.0, GPL-3.0, LGPL-2.1
  1. <?php
  2. /**
  3. * Filename.......: class.html.mime.mail.inc
  4. * Project........: HTML Mime mail class
  5. * Last Modified..: $Date: 2007/06/19 15:29:18 $
  6. * CVS Revision...: $Revision: 1.1 $
  7. * Copyright......: 2001, 2002 Richard Heyes
  8. */
  9. require_once(dirname(__FILE__) . '/mimePart.php');
  10. class htmlMimeMail
  11. {
  12. /**
  13. * The html part of the message
  14. * @var string
  15. */
  16. var $html;
  17. /**
  18. * The text part of the message(only used in TEXT only messages)
  19. * @var string
  20. */
  21. var $text;
  22. /**
  23. * The main body of the message after building
  24. * @var string
  25. */
  26. var $output;
  27. /**
  28. * The alternative text to the HTML part (only used in HTML messages)
  29. * @var string
  30. */
  31. var $html_text;
  32. /**
  33. * An array of embedded images/objects
  34. * @var array
  35. */
  36. var $html_images;
  37. /**
  38. * An array of recognised image types for the findHtmlImages() method
  39. * @var array
  40. */
  41. var $image_types;
  42. /**
  43. * Parameters that affect the build process
  44. * @var array
  45. */
  46. var $build_params;
  47. /**
  48. * Array of attachments
  49. * @var array
  50. */
  51. var $attachments;
  52. /**
  53. * The main message headers
  54. * @var array
  55. */
  56. var $headers;
  57. /**
  58. * Whether the message has been built or not
  59. * @var boolean
  60. */
  61. var $is_built;
  62. /**
  63. * The return path address. If not set the From:
  64. * address is used instead
  65. * @var string
  66. */
  67. var $return_path;
  68. /**
  69. * Array of information needed for smtp sending
  70. * @var array
  71. */
  72. var $smtp_params;
  73. /**
  74. * Constructor function. Sets the headers
  75. * if supplied.
  76. */
  77. function htmlMimeMail()
  78. {
  79. /**
  80. * Initialise some variables.
  81. */
  82. $this->html_images = array();
  83. $this->headers = array();
  84. $this->is_built = false;
  85. /**
  86. * If you want the auto load functionality
  87. * to find other image/file types, add the
  88. * extension and content type here.
  89. */
  90. $this->image_types = array(
  91. 'gif' => 'image/gif',
  92. 'jpg' => 'image/jpeg',
  93. 'jpeg' => 'image/jpeg',
  94. 'jpe' => 'image/jpeg',
  95. 'bmp' => 'image/bmp',
  96. 'png' => 'image/png',
  97. 'tif' => 'image/tiff',
  98. 'tiff' => 'image/tiff',
  99. 'swf' => 'application/x-shockwave-flash'
  100. );
  101. /**
  102. * Set these up
  103. */
  104. $this->build_params['html_encoding'] = 'quoted-printable';
  105. $this->build_params['text_encoding'] = '7bit';
  106. $this->build_params['html_charset'] = 'UTF-8';
  107. $this->build_params['text_charset'] = 'UTF-8';
  108. //$this->build_params['head_charset'] = 'ISO-8859-1';
  109. $this->build_params['head_charset'] = 'UTF-8';
  110. $this->build_params['text_wrap'] = 998;
  111. /**
  112. * Defaults for smtp sending
  113. */
  114. if (!empty($GLOBALS['HTTP_SERVER_VARS']['HTTP_HOST'])) {
  115. $helo = $GLOBALS['HTTP_SERVER_VARS']['HTTP_HOST'];
  116. } elseif (!empty($GLOBALS['HTTP_SERVER_VARS']['SERVER_NAME'])) {
  117. $helo = $GLOBALS['HTTP_SERVER_VARS']['SERVER_NAME'];
  118. } else {
  119. $helo = 'localhost';
  120. }
  121. $this->smtp_params['host'] = 'localhost';
  122. $this->smtp_params['port'] = 25;
  123. $this->smtp_params['helo'] = $helo;
  124. $this->smtp_params['auth'] = false;
  125. $this->smtp_params['user'] = '';
  126. $this->smtp_params['pass'] = '';
  127. /**
  128. * Make sure the MIME version header is first.
  129. */
  130. $this->headers['MIME-Version'] = '1.0';
  131. }
  132. /**
  133. * This function will read a file in
  134. * from a supplied filename and return
  135. * it. This can then be given as the first
  136. * argument of the the functions
  137. * add_html_image() or add_attachment().
  138. */
  139. function getFile($filename)
  140. {
  141. $return = '';
  142. if ($fp = fopen($filename, 'rb')) {
  143. while (!feof($fp)) {
  144. $return .= fread($fp, 1024);
  145. }
  146. fclose($fp);
  147. return $return;
  148. } else {
  149. return false;
  150. }
  151. }
  152. /**
  153. * Accessor to set the CRLF style
  154. */
  155. function setCrlf($crlf = "\n")
  156. {
  157. if (!defined('CRLF')) {
  158. define('CRLF', $crlf, true);
  159. }
  160. if (!defined('MAIL_MIMEPART_CRLF')) {
  161. define('MAIL_MIMEPART_CRLF', $crlf, true);
  162. }
  163. }
  164. /**
  165. * Accessor to set the SMTP parameters
  166. */
  167. function setSMTPParams($host = null, $port = null, $helo = null, $auth = null, $user = null, $pass = null)
  168. {
  169. if (!is_null($host)) $this->smtp_params['host'] = $host;
  170. if (!is_null($port)) $this->smtp_params['port'] = $port;
  171. if (!is_null($helo)) $this->smtp_params['helo'] = $helo;
  172. if (!is_null($auth)) $this->smtp_params['auth'] = $auth;
  173. if (!is_null($user)) $this->smtp_params['user'] = $user;
  174. if (!is_null($pass)) $this->smtp_params['pass'] = $pass;
  175. }
  176. /**
  177. * Accessor function to set the text encoding
  178. */
  179. function setTextEncoding($encoding = '7bit')
  180. {
  181. $this->build_params['text_encoding'] = $encoding;
  182. }
  183. /**
  184. * Accessor function to set the HTML encoding
  185. */
  186. function setHtmlEncoding($encoding = 'quoted-printable')
  187. {
  188. $this->build_params['html_encoding'] = $encoding;
  189. }
  190. /**
  191. * Accessor function to set the text charset
  192. */
  193. function setTextCharset($charset = 'ISO-8859-1')
  194. {
  195. $this->build_params['text_charset'] = $charset;
  196. }
  197. /**
  198. * Accessor function to set the HTML charset
  199. */
  200. function setHtmlCharset($charset = 'ISO-8859-1')
  201. {
  202. $this->build_params['html_charset'] = $charset;
  203. }
  204. /**
  205. * Accessor function to set the header encoding charset
  206. */
  207. function setHeadCharset($charset = 'ISO-8859-1')
  208. {
  209. $this->build_params['head_charset'] = $charset;
  210. }
  211. /**
  212. * Accessor function to set the text wrap count
  213. */
  214. function setTextWrap($count = 998)
  215. {
  216. $this->build_params['text_wrap'] = $count;
  217. }
  218. /**
  219. * Accessor to set a header
  220. */
  221. function setHeader($name, $value)
  222. {
  223. $this->headers[$name] = $value;
  224. }
  225. /**
  226. * Accessor to add a Subject: header
  227. */
  228. function setSubject($subject)
  229. {
  230. $this->headers['Subject'] = $subject;
  231. }
  232. /**
  233. * Accessor to add a From: header
  234. */
  235. function setFrom($from)
  236. {
  237. $this->headers['From'] = $from;
  238. }
  239. /**
  240. * Accessor to set the return path
  241. */
  242. function setReturnPath($return_path)
  243. {
  244. $this->return_path = $return_path;
  245. }
  246. /**
  247. * Accessor to add a Cc: header
  248. */
  249. function setCc($cc)
  250. {
  251. $this->headers['Cc'] = $cc;
  252. }
  253. /**
  254. * Accessor to add a Bcc: header
  255. */
  256. function setBcc($bcc)
  257. {
  258. $this->headers['Bcc'] = $bcc;
  259. }
  260. /**
  261. * Adds plain text. Use this function
  262. * when NOT sending html email
  263. */
  264. function setText($text = '')
  265. {
  266. $this->text = $text;
  267. }
  268. /**
  269. * Adds a html part to the mail.
  270. * Also replaces image names with
  271. * content-id's.
  272. */
  273. function setHtml($html, $text = null, $images_dir = null)
  274. {
  275. $this->html = $html;
  276. $this->html_text = $text;
  277. if (isset($images_dir)) {
  278. $this->_findHtmlImages($images_dir);
  279. }
  280. }
  281. /**
  282. * Function for extracting images from
  283. * html source. This function will look
  284. * through the html code supplied by add_html()
  285. * and find any file that ends in one of the
  286. * extensions defined in $obj->image_types.
  287. * If the file exists it will read it in and
  288. * embed it, (not an attachment).
  289. *
  290. * @author Dan Allen
  291. */
  292. function _findHtmlImages($images_dir)
  293. {
  294. // Build the list of image extensions
  295. while (list($key,) = each($this->image_types)) {
  296. $extensions[] = $key;
  297. }
  298. preg_match_all('/(?:"|\')([^"\']+\.('.implode('|', $extensions).'))(?:"|\')/Ui', $this->html, $images);
  299. for ($i=0; $i<count($images[1]); $i++) {
  300. if (file_exists($images_dir . $images[1][$i])) {
  301. $html_images[] = $images[1][$i];
  302. $this->html = str_replace($images[1][$i], basename($images[1][$i]), $this->html);
  303. }
  304. }
  305. if (!empty($html_images)) {
  306. // If duplicate images are embedded, they may show up as attachments, so remove them.
  307. $html_images = array_unique($html_images);
  308. sort($html_images);
  309. for ($i=0; $i<count($html_images); $i++) {
  310. if ($image = $this->getFile($images_dir.$html_images[$i])) {
  311. $ext = substr($html_images[$i], strrpos($html_images[$i], '.') + 1);
  312. $content_type = $this->image_types[strtolower($ext)];
  313. $this->addHtmlImage($image, basename($html_images[$i]), $content_type);
  314. }
  315. }
  316. }
  317. }
  318. /**
  319. * Adds an image to the list of embedded
  320. * images.
  321. */
  322. function addHtmlImage($file, $name = '', $c_type='application/octet-stream')
  323. {
  324. $this->html_images[] = array(
  325. 'body' => $file,
  326. 'name' => $name,
  327. 'c_type' => $c_type,
  328. 'cid' => md5(uniqid(time()))
  329. );
  330. }
  331. /**
  332. * Adds a file to the list of attachments.
  333. */
  334. function addAttachment($file, $name = '', $c_type='application/octet-stream', $encoding = 'base64')
  335. {
  336. $this->attachments[] = array(
  337. 'body' => $file,
  338. 'name' => $name,
  339. 'c_type' => $c_type,
  340. 'encoding' => $encoding
  341. );
  342. }
  343. /**
  344. * Adds a text subpart to a mime_part object
  345. */
  346. function &_addTextPart(&$obj, $text)
  347. {
  348. $params['content_type'] = 'text/plain';
  349. $params['encoding'] = $this->build_params['text_encoding'];
  350. $params['charset'] = $this->build_params['text_charset'];
  351. if (is_object($obj)) {
  352. return $obj->addSubpart($text, $params);
  353. } else {
  354. return new Mail_mimePart($text, $params);
  355. }
  356. }
  357. /**
  358. * Adds a html subpart to a mime_part object
  359. */
  360. function &_addHtmlPart(&$obj)
  361. {
  362. $params['content_type'] = 'text/html';
  363. $params['encoding'] = $this->build_params['html_encoding'];
  364. $params['charset'] = $this->build_params['html_charset'];
  365. if (is_object($obj)) {
  366. return $obj->addSubpart($this->html, $params);
  367. } else {
  368. return new Mail_mimePart($this->html, $params);
  369. }
  370. }
  371. /**
  372. * Starts a message with a mixed part
  373. */
  374. function &_addMixedPart()
  375. {
  376. $params['content_type'] = 'multipart/mixed';
  377. return new Mail_mimePart('', $params);
  378. }
  379. /**
  380. * Adds an alternative part to a mime_part object
  381. */
  382. function &_addAlternativePart(&$obj)
  383. {
  384. $params['content_type'] = 'multipart/alternative';
  385. if (is_object($obj)) {
  386. return $obj->addSubpart('', $params);
  387. } else {
  388. return new Mail_mimePart('', $params);
  389. }
  390. }
  391. /**
  392. * Adds a html subpart to a mime_part object
  393. */
  394. function &_addRelatedPart(&$obj)
  395. {
  396. $params['content_type'] = 'multipart/related';
  397. if (is_object($obj)) {
  398. return $obj->addSubpart('', $params);
  399. } else {
  400. return new Mail_mimePart('', $params);
  401. }
  402. }
  403. /**
  404. * Adds an html image subpart to a mime_part object
  405. */
  406. function &_addHtmlImagePart(&$obj, $value)
  407. {
  408. $params['content_type'] = $value['c_type'];
  409. $params['encoding'] = 'base64';
  410. $params['disposition'] = 'inline';
  411. $params['dfilename'] = $value['name'];
  412. $params['cid'] = $value['cid'];
  413. $obj->addSubpart($value['body'], $params);
  414. }
  415. /**
  416. * Adds an attachment subpart to a mime_part object
  417. */
  418. function &_addAttachmentPart(&$obj, $value)
  419. {
  420. $params['content_type'] = $value['c_type'];
  421. $params['encoding'] = $value['encoding'];
  422. $params['disposition'] = 'attachment';
  423. $params['dfilename'] = $value['name'];
  424. $obj->addSubpart($value['body'], $params);
  425. }
  426. /**
  427. * Builds the multipart message from the
  428. * list ($this->_parts). $params is an
  429. * array of parameters that shape the building
  430. * of the message. Currently supported are:
  431. *
  432. * $params['html_encoding'] - The type of encoding to use on html. Valid options are
  433. * "7bit", "quoted-printable" or "base64" (all without quotes).
  434. * 7bit is EXPRESSLY NOT RECOMMENDED. Default is quoted-printable
  435. * $params['text_encoding'] - The type of encoding to use on plain text Valid options are
  436. * "7bit", "quoted-printable" or "base64" (all without quotes).
  437. * Default is 7bit
  438. * $params['text_wrap'] - The character count at which to wrap 7bit encoded data.
  439. * Default this is 998.
  440. * $params['html_charset'] - The character set to use for a html section.
  441. * Default is ISO-8859-1
  442. * $params['text_charset'] - The character set to use for a text section.
  443. * - Default is ISO-8859-1
  444. * $params['head_charset'] - The character set to use for header encoding should it be needed.
  445. * - Default is ISO-8859-1
  446. */
  447. function buildMessage($params = array())
  448. {
  449. if (!empty($params)) {
  450. while (list($key, $value) = each($params)) {
  451. $this->build_params[$key] = $value;
  452. }
  453. }
  454. if (!empty($this->html_images)) {
  455. foreach ($this->html_images as $value) {
  456. $this->html = str_replace($value['name'], 'cid:'.$value['cid'], $this->html);
  457. }
  458. }
  459. $null = null;
  460. $attachments = !empty($this->attachments) ? true : false;
  461. $html_images = !empty($this->html_images) ? true : false;
  462. $html = !empty($this->html) ? true : false;
  463. $text = isset($this->text) ? true : false;
  464. switch (true) {
  465. case $text AND !$attachments:
  466. $message = &$this->_addTextPart($null, $this->text);
  467. break;
  468. case !$text AND $attachments AND !$html:
  469. $message = &$this->_addMixedPart();
  470. for ($i=0; $i<count($this->attachments); $i++) {
  471. $this->_addAttachmentPart($message, $this->attachments[$i]);
  472. }
  473. break;
  474. case $text AND $attachments:
  475. $message = &$this->_addMixedPart();
  476. $this->_addTextPart($message, $this->text);
  477. for ($i=0; $i<count($this->attachments); $i++) {
  478. $this->_addAttachmentPart($message, $this->attachments[$i]);
  479. }
  480. break;
  481. case $html AND !$attachments AND !$html_images:
  482. if (!is_null($this->html_text)) {
  483. $message = &$this->_addAlternativePart($null);
  484. $this->_addTextPart($message, $this->html_text);
  485. $this->_addHtmlPart($message);
  486. } else {
  487. $message = &$this->_addHtmlPart($null);
  488. }
  489. break;
  490. case $html AND !$attachments AND $html_images:
  491. if (!is_null($this->html_text)) {
  492. $message = &$this->_addAlternativePart($null);
  493. $this->_addTextPart($message, $this->html_text);
  494. $related = &$this->_addRelatedPart($message);
  495. } else {
  496. $message = &$this->_addRelatedPart($null);
  497. $related = &$message;
  498. }
  499. $this->_addHtmlPart($related);
  500. for ($i=0; $i<count($this->html_images); $i++) {
  501. $this->_addHtmlImagePart($related, $this->html_images[$i]);
  502. }
  503. break;
  504. case $html AND $attachments AND !$html_images:
  505. $message = &$this->_addMixedPart();
  506. if (!is_null($this->html_text)) {
  507. $alt = &$this->_addAlternativePart($message);
  508. $this->_addTextPart($alt, $this->html_text);
  509. $this->_addHtmlPart($alt);
  510. } else {
  511. $this->_addHtmlPart($message);
  512. }
  513. for ($i=0; $i<count($this->attachments); $i++) {
  514. $this->_addAttachmentPart($message, $this->attachments[$i]);
  515. }
  516. break;
  517. case $html AND $attachments AND $html_images:
  518. $message = &$this->_addMixedPart();
  519. if (!is_null($this->html_text)) {
  520. $alt = &$this->_addAlternativePart($message);
  521. $this->_addTextPart($alt, $this->html_text);
  522. $rel = &$this->_addRelatedPart($alt);
  523. } else {
  524. $rel = &$this->_addRelatedPart($message);
  525. }
  526. $this->_addHtmlPart($rel);
  527. for ($i=0; $i<count($this->html_images); $i++) {
  528. $this->_addHtmlImagePart($rel, $this->html_images[$i]);
  529. }
  530. for ($i=0; $i<count($this->attachments); $i++) {
  531. $this->_addAttachmentPart($message, $this->attachments[$i]);
  532. }
  533. break;
  534. }
  535. if (isset($message)) {
  536. $output = $message->encode();
  537. $this->output = $output['body'];
  538. $this->headers = array_merge($this->headers, $output['headers']);
  539. // Add message ID header
  540. srand((double)microtime()*10000000);
  541. //$message_id = sprintf('<%s.%s@%s>', base_convert(time(), 10, 36), base_convert(rand(), 10, 36), !empty($GLOBALS['HTTP_SERVER_VARS']['HTTP_HOST']) ? $GLOBALS['HTTP_SERVER_VARS']['HTTP_HOST'] : $GLOBALS['HTTP_SERVER_VARS']['SERVER_NAME']);
  542. // *** don't want to show atrium.ryzom.com in headers, so forcing to www.ryzom.com
  543. $message_id = sprintf('<%s.%s@%s>', base_convert(time(), 10, 36), base_convert(rand(), 10, 36), 'www.ryzom.com');
  544. $this->headers['Message-ID'] = $message_id;
  545. $this->is_built = true;
  546. return true;
  547. } else {
  548. return false;
  549. }
  550. }
  551. /**
  552. * Function to encode a header if necessary
  553. * according to RFC2047
  554. */
  555. function _encodeHeader($input, $charset = 'ISO-8859-1')
  556. {
  557. preg_match_all('/(\w*[\x80-\xFF]+\w*)/', $input, $matches);
  558. foreach ($matches[1] as $value) {
  559. $replacement = preg_replace('/([\x80-\xFF])/e', '"=" . strtoupper(dechex(ord("\1")))', $value);
  560. $input = str_replace($value, '=?' . $charset . '?Q?' . $replacement . '?=', $input);
  561. }
  562. return $input;
  563. }
  564. /**
  565. * Sends the mail.
  566. *
  567. * @param array $recipients
  568. * @param string $type OPTIONAL
  569. * @return mixed
  570. */
  571. function send($recipients, $type = 'mail')
  572. {
  573. if (!defined('CRLF')) {
  574. $this->setCrlf($type == 'mail' ? "\n" : "\r\n");
  575. }
  576. if (!$this->is_built) {
  577. $this->buildMessage();
  578. }
  579. switch ($type) {
  580. case 'mail':
  581. $subject = '';
  582. if (!empty($this->headers['Subject'])) {
  583. $subject = $this->_encodeHeader($this->headers['Subject'], $this->build_params['head_charset']);
  584. unset($this->headers['Subject']);
  585. }
  586. // Get flat representation of headers
  587. foreach ($this->headers as $name => $value) {
  588. $headers[] = $name . ': ' . $this->_encodeHeader($value, $this->build_params['head_charset']);
  589. }
  590. $to = $this->_encodeHeader(implode(', ', $recipients), $this->build_params['head_charset']);
  591. if (!empty($this->return_path)) {
  592. $result = mail($to, $subject, $this->output, implode(CRLF, $headers), '-f' . $this->return_path);
  593. } else {
  594. $result = mail($to, $subject, $this->output, implode(CRLF, $headers));
  595. }
  596. // Reset the subject in case mail is resent
  597. if ($subject !== '') {
  598. $this->headers['Subject'] = $subject;
  599. }
  600. // Return
  601. return $result;
  602. break;
  603. case 'smtp':
  604. require_once(dirname(__FILE__) . '/smtp.php');
  605. require_once(dirname(__FILE__) . '/RFC822.php');
  606. $smtp = &smtp::connect($this->smtp_params);
  607. // Parse recipients argument for internet addresses
  608. foreach ($recipients as $recipient) {
  609. $addresses = Mail_RFC822::parseAddressList($recipient, $this->smtp_params['helo'], null, false);
  610. foreach ($addresses as $address) {
  611. $smtp_recipients[] = sprintf('%s@%s', $address->mailbox, $address->host);
  612. }
  613. }
  614. unset($addresses); // These are reused
  615. unset($address); // These are reused
  616. // Get flat representation of headers, parsing
  617. // Cc and Bcc as we go
  618. foreach ($this->headers as $name => $value) {
  619. if ($name == 'Cc' OR $name == 'Bcc') {
  620. $addresses = Mail_RFC822::parseAddressList($value, $this->smtp_params['helo'], null, false);
  621. foreach ($addresses as $address) {
  622. $smtp_recipients[] = sprintf('%s@%s', $address->mailbox, $address->host);
  623. }
  624. }
  625. if ($name == 'Bcc') {
  626. continue;
  627. }
  628. $headers[] = $name . ': ' . $this->_encodeHeader($value, $this->build_params['head_charset']);
  629. }
  630. // Add To header based on $recipients argument
  631. $headers[] = 'To: ' . $this->_encodeHeader(implode(', ', $recipients), $this->build_params['head_charset']);
  632. // Add headers to send_params
  633. $send_params['headers'] = $headers;
  634. $send_params['recipients'] = array_values(array_unique($smtp_recipients));
  635. $send_params['body'] = $this->output;
  636. // Setup return path
  637. if (isset($this->return_path)) {
  638. $send_params['from'] = $this->return_path;
  639. } elseif (!empty($this->headers['From'])) {
  640. $from = Mail_RFC822::parseAddressList($this->headers['From']);
  641. $send_params['from'] = sprintf('%s@%s', $from[0]->mailbox, $from[0]->host);
  642. } else {
  643. $send_params['from'] = 'postmaster@' . $this->smtp_params['helo'];
  644. }
  645. // Send it
  646. if (!$smtp->send($send_params)) {
  647. $this->errors = $smtp->errors;
  648. return false;
  649. }
  650. return true;
  651. break;
  652. }
  653. }
  654. /**
  655. * Use this method to return the email
  656. * in message/rfc822 format. Useful for
  657. * adding an email to another email as
  658. * an attachment. there's a commented
  659. * out example in example.php.
  660. */
  661. function getRFC822($recipients)
  662. {
  663. // Make up the date header as according to RFC822
  664. $this->setHeader('Date', date('D, d M y H:i:s O'));
  665. if (!defined('CRLF')) {
  666. $this->setCrlf($type == 'mail' ? "\n" : "\r\n");
  667. }
  668. if (!$this->is_built) {
  669. $this->buildMessage();
  670. }
  671. // Return path ?
  672. if (isset($this->return_path)) {
  673. $headers[] = 'Return-Path: ' . $this->return_path;
  674. }
  675. // Get flat representation of headers
  676. foreach ($this->headers as $name => $value) {
  677. $headers[] = $name . ': ' . $value;
  678. }
  679. $headers[] = 'To: ' . implode(', ', $recipients);
  680. return implode(CRLF, $headers) . CRLF . CRLF . $this->output;
  681. }
  682. } // End of class.
  683. ?>