PageRenderTime 52ms CodeModel.GetById 12ms RepoModel.GetById 0ms app.codeStats 0ms

/dotproject/classes/libmail.class.php

http://github.com/BigBlueHat/dotproject
PHP | 865 lines | 598 code | 60 blank | 207 comment | 87 complexity | c8b34a38ec3fe92e0da9d08540b5b306 MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.1
  1. <?php /* CLASSES $Id$ */
  2. /**
  3. * @package dotproject
  4. * @subpackage utilites
  5. */
  6. if (!defined('DP_BASE_DIR')) {
  7. die('You should not access this file directly.');
  8. }
  9. /**
  10. * This class encapsulates the PHP mail() function.
  11. *
  12. * implements CC, Bcc, Priority headers
  13. * @version 1.3
  14. * <ul>
  15. * <li>added ReplyTo($address) method
  16. * <li>added Receipt() method - to add a mail receipt
  17. * <li>added optionnal charset parameter to Body() method. this should fix charset problem on some mail clients
  18. * </ul>
  19. * Example
  20. * <code>
  21. * include "libmail.php";
  22. *
  23. * $m= new Mail; // create the mail
  24. * $m->From("leo@isp.com");
  25. * $m->To("destination@somewhere.fr");
  26. * $m->Subject("the subject of the mail");
  27. *
  28. * $message= "Hello world!\nthis is a test of the Mail class\nplease ignore\nThanks.";
  29. * $m->Body($message); // set the body
  30. * $m->Cc("someone@somewhere.fr");
  31. * $m->Bcc("someoneelse@somewhere.fr");
  32. * $m->Priority(4) ; // set the priority to Low
  33. * $m->Attach("/home/leo/toto.gif", "image/gif") ; // attach a file of type image/gif
  34. * $m->Send(); // send the mail
  35. * echo "the mail below has been sent:<br><pre>", $m->Get(), "</pre>";
  36. * </code>
  37. LASTMOD
  38. Fri Aug 03 17:03:25 UTC 2007
  39. * @author Leo West - lwest@free.fr
  40. * @author Emiliano Gabrielli - emiliano.gabrielli@dearchitettura.com
  41. */
  42. class Mail
  43. {
  44. /**
  45. * list of To addresses
  46. * @var array
  47. */
  48. var $ato = array();
  49. /**
  50. * @var array
  51. */
  52. var $acc = array();
  53. /**
  54. * @var array
  55. */
  56. var $abcc = array();
  57. /**
  58. * paths of attached files
  59. * @var array
  60. */
  61. var $aattach = array();
  62. /**
  63. * list of message headers
  64. * @var array
  65. */
  66. var $xheaders = array();
  67. /**
  68. * string version of message headers in the form
  69. * "HeaderName: header content\r\n"
  70. * @var string
  71. */
  72. var $headers = '';
  73. /**
  74. * message priorities referential
  75. * @var array
  76. */
  77. var $priorities = array('1 (Highest)', '2 (High)', '3 (Normal)', '4 (Low)', '5 (Lowest)');
  78. /**
  79. * character set of message
  80. * @var string
  81. */
  82. var $charset;
  83. var $ctencoding;
  84. var $receipt = 0;
  85. var $useRawAddress = TRUE;
  86. var $host;
  87. var $port;
  88. var $sasl;
  89. var $tls;
  90. var $username;
  91. var $password;
  92. var $transport;
  93. var $defer;
  94. var $response;
  95. var $err = false;
  96. var $last_error = false;
  97. /**
  98. * Mail constructor
  99. */
  100. function Mail() {
  101. $this->autoCheck(TRUE);
  102. $this->boundary = '--' . md5(uniqid('dPboundary'));
  103. // Grab the current mail handling options
  104. $this->transport = dPgetConfig('mail_transport', 'php');
  105. $this->host = dPgetConfig('mail_host', 'localhost');
  106. $this->port = dPgetConfig('mail_port', '25');
  107. $this->sasl = dPgetConfig('mail_auth', FALSE);
  108. $this->tls = dPgetConfig('mail_smtp_tls', FALSE);
  109. $this->username = dPgetConfig('mail_user');
  110. $this->password = dPgetConfig('mail_pass');
  111. $this->defer = dPgetConfig('mail_defer');
  112. $this->timeout = dPgetConfig('mail_timeout', 0);
  113. $this->charset = ((isset($GLOBALS['locale_char_set']))
  114. ? mb_strtolower($GLOBALS['locale_char_set']) : 'us-ascii');
  115. $this->ctencoding = $this->charset != 'us-ascii' ? '8bit' : '7bit';
  116. $this->canEncode = 'us-ascii' != $this->charset;
  117. $this->hasMbStr = function_exists('mb_substr');
  118. }
  119. /**
  120. * activate or desactivate the email addresses validator
  121. *
  122. * ex: autoCheck(TRUE) turn the validator on
  123. * by default autoCheck feature is on
  124. *
  125. * @param boolean $bool set to TRUE to turn on the auto validation
  126. * @access public
  127. */
  128. function autoCheck($bool) {
  129. $this->checkAddress = (bool) $bool;
  130. }
  131. /**
  132. * Define the subject line of the email
  133. * @param string $subject any monoline string
  134. * @param string $charset encoding to be used for Quoted-Printable encoding of the subject
  135. */
  136. function Subject($subject, $charset='') {
  137. global $AppUI;
  138. if (!empty($charset)) {
  139. $this->charset = mb_strtolower($charset);
  140. }
  141. $subject = dPgetConfig('email_prefix').' '.$subject;
  142. $subject = strtr($subject, "\x0B\0\t\r\n\f" , ' ');
  143. $subject = $this->_wordEncode($subject, mb_strlen('Subject: '));
  144. $this->xheaders['Subject'] = $subject;
  145. }
  146. /**
  147. * set the sender of the mail
  148. * @param string $from should be an email address
  149. */
  150. function From($from) {
  151. if (!is_string($from)) {
  152. return FALSE;
  153. }
  154. $from = strtr($from, "\x0B\0\t\r\n\f" , ' ');
  155. $this->xheaders['From'] = $this->_addressEncode($from, mb_strlen('From: '));
  156. }
  157. /**
  158. * set the Reply-to header
  159. * @param string $email should be an email address
  160. */
  161. function ReplyTo($address) {
  162. if (!is_string($address)) {
  163. return FALSE;
  164. }
  165. $address = strtr($address, "\x0B\0\t\r\n\f" , ' ');
  166. $this->xheaders['Reply-To'] = $this->_addressEncode($address, mb_strlen('Reply-To: '));
  167. }
  168. /**
  169. * add a receipt to the mail ie. a confirmation is returned to the "From" address
  170. * (or "ReplyTo" if defined) when the receiver opens the message.
  171. * @warning this functionality is *not* a standard, thus only some mail clients are compliants.
  172. */
  173. function Receipt() {
  174. $this->receipt = 1;
  175. }
  176. /**
  177. * set the mail recipient
  178. *
  179. * The optional reset parameter is useful when looping through records to send individual mails.
  180. * This prevents the 'to' array being continually stacked with additional addresses.
  181. *
  182. * @param string $to email address, accept both a single address or an array of addresses
  183. * @param boolean $reset resets the current array
  184. */
  185. function To($to, $reset=FALSE) {
  186. if (is_array($to)) {
  187. $to = array_map(create_function('$s', 'return strtr($s, "\x0B\0\t\r\n\f", " ");'), $to);
  188. $this->ato = $to;
  189. } else {
  190. $to = strtr($to, "\x0B\0\t\r\n\f", ' ');
  191. if ($this->useRawAddress) {
  192. if (preg_match("/^(.*)\<(.+)\>$/D", $to, $regs)) {
  193. $to = $regs[2];
  194. }
  195. }
  196. if ($reset) {
  197. unset($this->ato);
  198. $this->ato = array();
  199. }
  200. $this->ato[] = $to;
  201. }
  202. if ($this->checkAddress == TRUE) {
  203. $this->CheckAdresses($this->ato);
  204. }
  205. }
  206. /**
  207. * Cc()
  208. * set the CC headers (carbon copy)
  209. * $cc : email address(es), accept both array and string
  210. */
  211. function Cc($cc) {
  212. if (is_array($cc)) {
  213. $cc = array_map(create_function('$s', 'return strtr($s, "\x0B\0\t\r\n\f", " ");'), $cc);
  214. $this->acc = $cc;
  215. } else {
  216. $cc = strtr($cc, "\x0B\0\t\r\n\f", ' ');
  217. $this->acc = explode(',', $cc);
  218. }
  219. if ($this->checkAddress == TRUE) {
  220. $this->CheckAdresses($this->acc);
  221. }
  222. }
  223. /**
  224. * set the Bcc headers (blank carbon copy).
  225. * $bcc : email address(es), accept both array and string
  226. */
  227. function Bcc($bcc) {
  228. if (is_array($bcc)) {
  229. $bcc = array_map(create_function('$s', 'return strtr($s, "\x0B\0\t\r\n\f", " ");'), $bcc);
  230. $this->abcc = $bcc;
  231. } else {
  232. $bcc = strtr($bcc, "\x0B\0\t\r\n\f", ' ');
  233. $this->abcc = explode(',', $bcc);
  234. }
  235. if ($this->checkAddress == TRUE) {
  236. $this->CheckAdresses($this->abcc);
  237. }
  238. }
  239. /**
  240. * set the body (message) of the mail
  241. * define the charset if the message contains extended characters (accents)
  242. * default to us-ascii
  243. * $mail->Body("m?l en fran?ais avec des accents", "iso-8859-1");
  244. */
  245. function Body($body, $charset='') {
  246. $this->body = $body;
  247. if (!empty($charset)) {
  248. $this->charset = mb_strtolower($charset);
  249. if ($this->charset != 'us-ascii') {
  250. $this->ctencoding = '8bit';
  251. }
  252. }
  253. }
  254. /**
  255. * set the Organization header
  256. */
  257. function Organization($org) {
  258. if (trim($org) != '') {
  259. $this->xheaders['Organization'] = $this->_wordEncode($org, mb_strlen('Organization: '));
  260. }
  261. }
  262. /**
  263. * set the mail priority
  264. * $priority : integer taken between 1 (highest) and 5 (lowest)
  265. * ex: $mail->Priority(1) ; => Highest
  266. */
  267. function Priority($priority) {
  268. if (! intval($priority)) {
  269. return FALSE;
  270. }
  271. if (! isset($this->priorities[$priority-1])) {
  272. return FALSE;
  273. }
  274. $this->xheaders['X-Priority'] = $this->priorities[$priority-1];
  275. return TRUE;
  276. }
  277. /**
  278. * Attach a file to the mail
  279. *
  280. * @param string $filename : path of the file to attach
  281. * @param string $filetype : MIME-type of the file. default to 'application/x-unknown-content-type'
  282. * @param string $disposition : instruct the Mailclient to display the file if possible ("inline")
  283. * or always as a link ("attachment") possible values are "inline",
  284. * "attachment"
  285. */
  286. function Attach($filename, $filetype='', $disposition='inline') {
  287. // TODO : si filetype="", alors chercher dans un tablo de MT connus / extension du fichier
  288. if (empty($filetype)) {
  289. $filetype = 'application/x-unknown-content-type';
  290. }
  291. $this->aattach[] = $filename;
  292. $this->actype[] = $filetype;
  293. $this->adispo[] = $disposition;
  294. }
  295. /**
  296. * Build the email message
  297. * @access protected
  298. */
  299. function BuildMail() {
  300. global $AppUI;
  301. // build the headers
  302. if (count($this->ato) > 0) {
  303. $this->_addressesEncode($this->ato, 'To');
  304. }
  305. if (count($this->acc) > 0) {
  306. $this->_addressesEncode($this->acc, 'CC');
  307. }
  308. if (count($this->abcc) > 0) {
  309. $this->_addressesEncode($this->abcc, 'BCC');
  310. }
  311. if ($this->receipt) {
  312. if (isset($this->xheaders['Reply-To'])) {
  313. $this->xheaders['Disposition-Notification-To'] = $this->xheaders['Reply-To'];
  314. } else {
  315. $this->xheaders['Disposition-Notification-To'] = $this->xheaders['From'];
  316. }
  317. }
  318. if (! empty($this->charset)) {
  319. $this->xheaders['Mime-Version'] = '1.0';
  320. $this->xheaders['Content-Type'] = 'text/plain; charset=' . $this->charset;
  321. $this->xheaders['Content-Transfer-Encoding'] = $this->ctencoding;
  322. }
  323. $this->xheaders['X-Mailer'] = 'dotProject v' . $AppUI->getVersion();
  324. $this->headers = '';
  325. foreach ($this->xheaders as $h=>$v) {
  326. $this->headers .= $h . ': ' . trim($v) . "\r\n";
  327. }
  328. // include attached files
  329. if (count($this->aattach) > 0) {
  330. $this->_build_attachement();
  331. } else {
  332. $sep = "\r\n";
  333. $arr = preg_split("/(\r?\n)|\r/", $this->body);
  334. $this->fullBody = implode($sep, $arr);
  335. }
  336. }
  337. /**
  338. * format and send the mail
  339. * @access public
  340. */
  341. function Send() {
  342. $this->BuildMail();
  343. if ($this->defer) {
  344. return $this->QueueMail();
  345. } else if ($this->transport == 'smtp') {
  346. return $this->SMTPSend();
  347. } else {
  348. $headers = '';
  349. foreach ($this->xheaders as $k => $v) {
  350. if ($k == 'To' || $k == 'Subject') {
  351. continue;
  352. }
  353. $headers .= $k . ': ' . trim($v) . "\r\n";
  354. }
  355. return @mail($this->xheaders['To'], $this->xheaders['Subject'], $this->fullBody, $headers);
  356. }
  357. }
  358. /**
  359. * Send email via an SMTP connection.
  360. *
  361. * Work based loosly on that of Bugs Genie, which appears to be in turn based on something from 'Ninebirds'
  362. *
  363. * @access public
  364. */
  365. function SMTPSend() {
  366. global $AppUI;
  367. // Start the connection to the server
  368. $error_number = 0;
  369. $error_message = '';
  370. $headers =& $this->xheaders;
  371. $this->socket = fsockopen($this->host, $this->port, $error_number, $error_message, $this->timeout);
  372. if (! $this->socket) {
  373. dprint(__FILE__, __LINE__, 1, ('Error on connecting to host ' . $this->host . ' at port '
  374. . $this->port . ': ' . $error_message . ' ('
  375. . $error_number . ')'));
  376. $AppUI->setMsg('Cannot connect to SMTP Host: ' . $error_message
  377. . ' (' . $error_number . ')');
  378. return FALSE;
  379. }
  380. // Read the opening stuff;
  381. $this->socketReadPattern(220, 300);
  382. // Send the ESMTP protocol "hello"
  383. $this->socketSend('EHLO ' . $this->getHostName());
  384. $reply = $this->socketReadPattern(250, 300, 500, 502, 503);
  385. // If ESMTP fails and TLS not needed, try the standard SMTP protocol "hello"
  386. if ($reply[0] == '5' && !($this->tls)) {
  387. $this->err = FALSE;
  388. $this->socketSend('HELO ' . $this->getHostName());
  389. $this->socketReadPattern(250, 300);
  390. }
  391. if ($this->err) {
  392. dprint(__FILE__, __LINE__, 1,
  393. ('Failed to initiate connection to server: ' . implode("\n", $this->response)));
  394. $AppUI->setMsg('Failed to initiate connection to SMTP server: ' . $this->last_error);
  395. fclose($this->socket);
  396. return FALSE;
  397. }
  398. if ($this->tls) {
  399. $this->socketSend('STARTTLS');
  400. $this->socketReadPattern(220);
  401. if ($this->err) {
  402. dprint(__FILE__, __LINE__, 1,
  403. ('TLS Initialization failed on server: ' . implode("\n", $this->response)));
  404. $AppUI->setMsg('Failed to login to SMTP server: ' . $this->last_error);
  405. fclose($this->socket);
  406. return FALSE;
  407. }
  408. $tries = 0;
  409. do {
  410. if ($tries) {
  411. sleep($tries);
  412. }
  413. $tls_connection = stream_socket_enable_crypto($this->socket, $this->tls,
  414. STREAM_CRYPTO_METHOD_TLS_CLIENT);
  415. } while ($tls_connection === 0 && (($tries * ($tries + 1)) / 2) < $this->timeout);
  416. if (! $tls_connection) {
  417. dprint(__FILE__, __LINE__, 1,
  418. ('TLS Connection failed on server: ' . implode("\n", $this->response)));
  419. $AppUI->setMsg('Failed to login to SMTP server: ' . $this->last_error);
  420. fclose($this->socket);
  421. return FALSE;
  422. }
  423. }
  424. if ($this->sasl && $this->username) {
  425. $this->socketSend('AUTH LOGIN');
  426. $this->socketReadPattern(334);
  427. if (! $this->err) {
  428. $this->socketSend(base64_encode($this->username));
  429. $this->socketReadPattern(334);
  430. }
  431. if (! $this->err) {
  432. $this->socketSend(base64_encode($this->password));
  433. $rcv = $this->socketReadPattern(235);
  434. }
  435. if ($this->err) {
  436. dprint(__FILE__, __LINE__, 1,
  437. ('Authentication failed on server: ' . implode("\n", $this->response)));
  438. $AppUI->setMsg('Failed to login to SMTP server: ' . $this->last_error);
  439. fclose($this->socket);
  440. return FALSE;
  441. }
  442. }
  443. // Determine the mail from address.
  444. if (! isset($headers['From'])) {
  445. $from = dPgetConfig('admin_user') . '@' . dPgetConfig('site_domain');
  446. } else {
  447. // Search for the parts of the email address
  448. $from = ((preg_match('/.*<([^@]+@[a-z0-9\._-]+)>/i', $headers['From'], $matches))
  449. ? $matches[1] : $headers['From']);
  450. }
  451. $this->socketSend("MAIL FROM: <$from>");
  452. $rcv = $this->socketReadPattern(250, 300);
  453. if ($this->err) {
  454. $AppUI->setMsg('Failed to send email: ' . $this->last_error, UI_MSG_ERROR);
  455. return FALSE;
  456. }
  457. foreach ($this->ato as $to_address) {
  458. if (mb_strpos($to_address, '<') !== FALSE) {
  459. preg_match('/^.*<([^@]+\@[a-z0-9\._-]+)>/i', $to_address, $matches);
  460. if (isset($matches[1]))
  461. $to_address = $matches[1];
  462. }
  463. $this->socketSend("RCPT TO: <$to_address>");
  464. $rcv = $this->socketReadPattern(array(250,251), 300);
  465. if ($this->err) {
  466. $AppUI->setMsg('Failed to send email: ' . $this->last_error, UI_MSG_ERROR);
  467. return FALSE;
  468. }
  469. }
  470. $this->socketSend('DATA');
  471. $rcv = $this->socketReadPattern(354, 120);
  472. if ($this->err) {
  473. $AppUI->setMsg('Failed to send email: ' . $this->last_error, UI_MSG_ERROR);
  474. return FALSE;
  475. }
  476. foreach ($headers as $hdr => $val) {
  477. $this->socketSend("$hdr: $val");
  478. }
  479. // Now build the To Headers as well.
  480. $this->socketSend('Date: ' . date('r'));
  481. $this->socketSend('');
  482. $this->socketSend($this->fullBody);
  483. $this->socketSend('.');
  484. $result = $this->socketReadPattern(250, 600);
  485. if ($this->err) {
  486. dprint(__FILE__, __LINE__, 1, ('Failed to send email from ' . $from . ' to ' . $to_address
  487. . ': ' . implode("\n", $this->response)));
  488. $AppUI->setMsg('Failed to send email: ' . $this->last_error);
  489. return FALSE;
  490. }
  491. $this->socketSend('QUIT');
  492. $this->socketReadPattern(221, 300);
  493. // Don't error at this stage, but return regardless.
  494. return true;
  495. }
  496. function socketRead($timeout = null) {
  497. if ($timeout !== null) {
  498. stream_set_timeout($this->socket, $timeout);
  499. }
  500. $result = fgets($this->socket, 4096);
  501. dprint(__FILE__, __LINE__, 12, 'server said: ' . $result);
  502. $info = stream_get_meta_data($this->socket);
  503. if (!empty($info['timed_out'])) {
  504. $this->err = true;
  505. return false;
  506. }
  507. if ($result === false) {
  508. $this->err = true;
  509. }
  510. return $result;
  511. }
  512. /**
  513. * Using the method used by Zend_Mail to handle the SMTP protocol
  514. */
  515. function socketReadPattern($pattern, $timeout = null) {
  516. $this->response = array();
  517. $this->last_error = '';
  518. $cmd = '';
  519. $msg = '';
  520. if (!is_array($pattern)) {
  521. $pattern = array($pattern);
  522. }
  523. do {
  524. $this->response[] = $result = $this->socketRead($timeout);
  525. sscanf($result, '%d%s', $cmd, $msg);
  526. if ($cmd === null || ! in_array($cmd, $pattern)) {
  527. $this->err = true;
  528. $this->last_error = $result;
  529. return false;
  530. }
  531. } while (mb_strpos($msg, '-') === 0);
  532. return $msg;
  533. }
  534. function socketSend($msg, $rcv = FALSE) {
  535. dprint(__FILE__, __LINE__, 12, 'sending: ' . $msg);
  536. $this->err = false;
  537. $sent = fputs($this->socket, $msg . "\r\n");
  538. return (($rcv) ? $this->socketRead() : $sent);
  539. }
  540. function getHostName() {
  541. // Grab the server address, return a hostname for it.
  542. return (($host = gethostbyaddr($_SERVER['SERVER_ADDR'])) ? $host : 'localhost');
  543. }
  544. /**
  545. * Queue mail to allow the queue manager to trigger
  546. * the email transfer.
  547. *
  548. * @access private
  549. */
  550. function QueueMail() {
  551. global $AppUI;
  552. require_once $AppUI->getSystemClass('event_queue');
  553. $ec = new EventQueue;
  554. $vars = get_object_vars($this);
  555. return $ec->add(array('Mail', 'SendQueuedMail'), $vars, 'libmail', TRUE);
  556. }
  557. /**
  558. * Dequeue the email and transfer it. Called from the queue manager.
  559. *
  560. * @access private
  561. */
  562. function SendQueuedMail($mod, $type, $originator, $owner, &$args) {
  563. extract($args);
  564. if ($this->transport == 'smtp') {
  565. return $this->SMTPSend();
  566. } else {
  567. $headers = '';
  568. foreach ($xheaders as $k => $v) {
  569. if ($k == 'To' || $k == 'Subject') {
  570. continue;
  571. }
  572. $headers .= $k . ': ' . trim($v) . "\r\n";
  573. }
  574. return @mail($xheaders['To'], $xheaders['Subject'], $fullBody, $headers);
  575. }
  576. }
  577. /**
  578. * Returns the whole e-mail , headers + message
  579. * can be used for displaying the message in plain text or logging it
  580. *
  581. * @return string
  582. */
  583. function Get() {
  584. $this->BuildMail();
  585. $mail = $this->headers . "\r\n\r\n";
  586. $mail .= $this->fullBody;
  587. return $mail;
  588. }
  589. /**
  590. * check an email address validity
  591. * @access public
  592. * @param string $address : email address to check
  593. * @return TRUE if email adress is ok
  594. */
  595. function ValidEmail($address) {
  596. if (preg_match('/^(.*)\<(.+)\>$/D', $address, $regs)) {
  597. $address = $regs[2];
  598. }
  599. return (bool) preg_match('/^[^@ ]+@([-a-zA-Z0-9..]+)$/D', $address);
  600. }
  601. /**
  602. * check validity of email addresses
  603. * @param array $aad -
  604. * @return if unvalid, output an error message and exit, this may -should- be customized
  605. */
  606. function CheckAdresses($aad) {
  607. foreach ($aad as $ad ) {
  608. if (! $this->ValidEmail($ad)) {
  609. echo ('Class Mail, method Mail : invalid address ' . $ad);
  610. exit;
  611. }
  612. }
  613. return TRUE;
  614. }
  615. /**
  616. * alias for the mispelled CheckAdresses
  617. */
  618. function CheckAddresses($aad) {
  619. return $this->CheckAdresses($aad);
  620. }
  621. /**
  622. * check and encode attach file(s) . internal use only
  623. * @access private
  624. */
  625. function _build_attachement() {
  626. $this->xheaders['Content-Type'] = "multipart/mixed;\r\n boundary=\"" . $this->boundary .'"';
  627. $this->fullBody = "This is a multi-part message in MIME format.\r\n--".$this->boundary."\r\n";
  628. $this->fullBody .= ('Content-Type: text/plain; charset=' . $this->charset
  629. ."\r\nContent-Transfer-Encoding: " . $this->ctencoding . "\r\n\r\n");
  630. $sep= "\r\n";
  631. $body = preg_split("/\r?\n/", $this->body);
  632. $this->fullBody .= implode($sep, $body) ."\r\n";
  633. $ata= array();
  634. $k=0;
  635. // for each attached file, do...
  636. for ($i=0, $cnt = count($this->aattach); $i < $cnt; $i++) {
  637. $filename = $this->aattach[$i];
  638. $basename = basename($filename);
  639. $ctype = $this->actype[$i]; // content-type
  640. $disposition = $this->adispo[$i];
  641. if (! file_exists($filename)) {
  642. echo "Class Mail, method attach : file $filename can't be found";
  643. exit;
  644. }
  645. $subhdr = ('--' . $this->boundary . "\r\nContent-type: " . $ctype . ";\r\n"
  646. . ' name="' . $basename . '"' . "\r\n"
  647. . "Content-Transfer-Encoding: base64\r\n"
  648. . "Content-Disposition: " . "$disposition" . ";\r\n"
  649. . ' filename="' . "$basename" . '"' . "\r\n");
  650. $ata[$k++] = $subhdr;
  651. // non encoded line length
  652. $linesz= filesize($filename)+1;
  653. $fp= fopen($filename, 'rb');
  654. $ata[$k++] = chunk_split(base64_encode(fread($fp, $linesz)));
  655. fclose($fp);
  656. }
  657. $this->fullBody .= implode($sep, $ata);
  658. }
  659. /**
  660. * Encode an email address as RFC2047 wants
  661. * @author "Emiliano 'AlberT' Gabrielli" <emiliano.gabrielli@dearchitettura.com>
  662. * @access private
  663. *
  664. * @param string $addr: the string to be encoded
  665. * @param int $offset: an optional offset to be counted for the first line
  666. * @return string the encoded string
  667. */
  668. function _addressEncode($addr, $offset=0) {
  669. if (!$this->canEncode) {
  670. return $addr;
  671. }
  672. $matches = NULL;
  673. $mail = '';
  674. $txt = '';
  675. if (!@preg_match('/^(.*)(\?<[^@]+@[a-z0-9\._-]+>)$/Di', $addr, $matches)) {
  676. return $addr;
  677. }
  678. $txt = $matches[1];
  679. $mail = $matches[2];
  680. $txt = $this->_wordEncode($txt, $offset);
  681. return (($offset + $this->_mb_strlen($txt . $mail) > 76)
  682. ? ($txt . "\r\n " . $mail) : ($txt . $mail));
  683. }
  684. /**
  685. * Encode a string making it an encoded word as RFC2047 wants
  686. * @author "Emiliano 'AlberT' Gabrielli" <emiliano.gabrielli@dearchitettura.com>
  687. * @access private
  688. *
  689. * @param string $str: the string to be encoded
  690. * @param int $offset: an optional offset to be counted for the first line
  691. * @return string the encoded string, made of N encoded words, ignore length limits.
  692. */
  693. function _wordEncode($str, $offset=0) {
  694. if (!$this->canEncode) {
  695. return $str;
  696. }
  697. $cs = $this->charset;
  698. $qstr = $this->_utfToQuotedPrintable($str, $offset);
  699. $start_sentinel = "=?$cs?Q?";
  700. $end_sentinel = "?=";
  701. return ($start_sentinel . implode($end_sentinel . "\r\n\t" . $start_sentinel, $qstr)
  702. . $end_sentinel);
  703. }
  704. /**
  705. * Convert a UTF8 string into a quoted printable string, making sure
  706. * that the first line is a known number of characters long and subsequent
  707. * lines are <= 72 characters, and that utf8 characters are always encoded
  708. * completely on the one line.
  709. *
  710. * @author Adam Donnison <ajdonnison@dotproject.net>
  711. * @param string $str
  712. * @param integer $offset
  713. * @return array of lines of required length.
  714. */
  715. function _utfToQuotedPrintable($str, $offset=0) {
  716. $l = 72 - $offset;
  717. $result = array();
  718. $x = 0;
  719. $s = '';
  720. for ($i = 0, $len = mb_strlen($str); $i<$len; $i++) {
  721. $ord = ord($str[$i]);
  722. if ($ord > 32 && $ord < 127 && $str[$i] != '?' && $str[$i] != '=') {
  723. $s .= $str[$i];
  724. $x++;
  725. } else if (($ord & 0xE0) == 0xC0) {
  726. $s .= sprintf('=%02x=%02x', $ord, ord($str[++$i]));
  727. $x+=6;
  728. } else if (($ord & 0xF0) == 0xE0) {
  729. $s .= sprintf('=%02x=%02x=%02x', $ord, ord($str[++$i]), ord($str[++$i]));
  730. $x += 9;
  731. } else if (($ord & 0xF8) == 0xF0) {
  732. $s .= sprintf('=%02x=%02x=%02x=%02x', $ord, ord($str[++$i]), ord($str[++$i]),
  733. ord($str[++$i]));
  734. $x += 12;
  735. } else if (($ord & 0xFC) == 0xF8) {
  736. $s .= sprintf('=%02x=%02x=%02x=%02x=%02x', $ord, ord($str[++$i]), ord($str[++$i]),
  737. ord($str[++$i]), ord($str[++$i]));
  738. $x += 15;
  739. } else if (($ord & 0xFE) == 0xFC) {
  740. $s .= sprintf('=%02x=%02x=%02x=%02x=%02x=%02x', $ord, ord($str[++$i]), ord($str[++$i]),
  741. ord($str[++$i]), ord($str[++$i]), ord($str[++$i]));
  742. $x += 18;
  743. } else {
  744. $s .= sprintf('=%02x', $ord);
  745. $x += 3;
  746. }
  747. if ($x >= $l) {
  748. $result[] = $s;
  749. $s ='';
  750. $x = 0;
  751. $l = 72;
  752. }
  753. }
  754. if ($x) {
  755. $result[] = $s;
  756. }
  757. return $result;
  758. }
  759. function _addressesEncode(&$aaddr, $hdr) {
  760. $n = count($aaddr);
  761. $this->xheaders[$hdr] = $this->_addressEncode($aaddr[0], mb_strlen("$hdr: "));
  762. for ($i=1 /*skip first one*/; $i<$n; ++$i) {
  763. $val = $this->_addressEncode($aaddr[$i], 8);
  764. $val = trim($val);
  765. if ($val) {
  766. $this->xheaders[$hdr] .= (",\r\n" . $val);
  767. }
  768. }
  769. }
  770. function _strpos($str, $start, $offset=0){
  771. return (($this->hasMbStr)
  772. ? mb_strpos($str, $start, $offset, $this->charset) : strpos($str, $start, $offset));
  773. }
  774. function _substr($str, $start, $len=null) {
  775. if (NULL===$len) {
  776. $len = $this->_strlen($str);
  777. }
  778. return (($this->hasMbStr)
  779. ? mb_substr($str, $start, $len, $this->charset) : substr($str, $start, $len));
  780. }
  781. function _strlen($str) {
  782. return (($this->hasMbStr) ? mb_strlen($str, $this->charset) : strlen($str));
  783. }
  784. } // class Mail
  785. ?>