PageRenderTime 38ms CodeModel.GetById 10ms RepoModel.GetById 1ms app.codeStats 0ms

/wind/mail/WindMail.php

https://github.com/cuijinquan/nextwind
PHP | 594 lines | 299 code | 40 blank | 255 comment | 39 complexity | 2d871498056358fb99b3d44ca99c1c72 MD5 | raw file
  1. <?php
  2. Wind::import('WIND:mail.exception.WindMailException');
  3. /**
  4. * 邮件发送类
  5. *
  6. * @author Qian Su <aoxue.1988.su.qian@163.com>
  7. * @copyright ©2003-2103 phpwind.com
  8. * @license http://www.windframework.com
  9. * @version $Id: WindMail.php 3904 2013-01-08 07:01:26Z yishuo $
  10. * @package mail
  11. */
  12. class WindMail {
  13. /**
  14. *
  15. * @var array 邮件头
  16. */
  17. private $mailHeader = array();
  18. /**
  19. *
  20. * @var array 邮件附件
  21. */
  22. private $attachment = array();
  23. /**
  24. *
  25. * @var string 邮件字符集
  26. */
  27. private $charset = 'utf-8';
  28. /**
  29. *
  30. * @var string 是否是内嵌资源
  31. */
  32. private $embed = false;
  33. /**
  34. *
  35. * @var array 邮件收件人
  36. */
  37. private $recipients = null;
  38. /**
  39. *
  40. * @var string 邮件发件人
  41. */
  42. private $from = '';
  43. /**
  44. *
  45. * @var string 邮件消息体html展现方式
  46. */
  47. private $bodyHtml = '';
  48. /**
  49. *
  50. * @var string 邮件消息体文本展现方式
  51. */
  52. private $bodyText = '';
  53. /**
  54. *
  55. * @var array 邮件边界线
  56. */
  57. private $boundary;
  58. /**
  59. *
  60. * @var string 邮件编码方式
  61. */
  62. private $encode = self::ENCODE_BASE64;
  63. /**
  64. *
  65. * @var 内容类型
  66. */
  67. private $contentType;
  68. // 常用邮件MIME
  69. const CRLF = "\n";
  70. const TO = 'To';
  71. const CC = 'Cc';
  72. const BCC = 'Bcc';
  73. const FROM = 'From';
  74. const SUBJECT = 'Subject';
  75. const MESSAGEID = 'Message-Id';
  76. const CONTENTTYPE = 'Content-Type';
  77. const CONTENTENCODE = 'Content-Transfer-Encoding';
  78. const CONTENTID = 'Content-ID';
  79. const CONTENTPOSITION = 'Content-Disposition';
  80. const CONTENTDESCRIPT = 'Content-Description';
  81. const CONTENTLOCATION = 'Content-Location';
  82. const CONTENTLANGUAGE = 'Content-Language';
  83. const DATE = 'Date';
  84. // 邮件MIME类型
  85. const MIME_OCTETSTREAM = 'application/octet-stream';
  86. const MIME_TEXT = 'text/plain';
  87. const MIME_HTML = 'text/html';
  88. const MIME_ALTERNATIVE = 'multipart/alternative';
  89. const MIME_MIXED = 'multipart/mixed';
  90. const MIME_RELATED = 'multipart/related';
  91. // 邮件编码
  92. const ENCODE_7BIT = '7bit';
  93. const ENCODE_8BIT = '8bit';
  94. const ENCODE_QP = 'quoted-printable';
  95. const ENCODE_BASE64 = 'base64';
  96. const ENCODE_BINARY = 'binary';
  97. // 邮件编码内容
  98. const DIS_ATTACHMENT = 'attachment';
  99. const DIS_INLINE = 'inline';
  100. const LINELENGTH = 72;
  101. // 邮件发送方式
  102. const SEND_SMTP = 'smtp';
  103. const SEND_PHP = 'php';
  104. const SEND_SEND = 'send';
  105. /**
  106. * 发送邮件
  107. *
  108. * @param string $type 发送类型
  109. * @param array $config 邮件发送器需要的配置数据
  110. * @return boolean
  111. * @throws Exception
  112. */
  113. public function send($type = self::SEND_SMTP, $config = array()) {
  114. $class = Wind::import('Wind:mail.sender.Wind' . ucfirst($type) . 'Mail');
  115. /* @var $sender IWindSendMail */
  116. $sender = WindFactory::createInstance($class);
  117. return $sender->send($this, $config);
  118. }
  119. /**
  120. * 创建邮件头信息
  121. *
  122. * @return string
  123. */
  124. public function createHeader() {
  125. if (!isset($this->mailHeader[self::CONTENTTYPE])) {
  126. $type = self::MIME_TEXT;
  127. if ($this->attachment)
  128. $type = $this->embed ? self::MIME_RELATED : self::MIME_MIXED;
  129. elseif ($this->bodyHtml)
  130. $type = $this->bodyText ? self::MIME_ALTERNATIVE : self::MIME_HTML;
  131. $this->setContentType($type);
  132. }
  133. if (!isset($this->mailHeader[self::CONTENTENCODE])) $this->setContentEncode();
  134. $header = '';
  135. foreach ($this->mailHeader as $key => $value) {
  136. if (!$value) continue;
  137. $header .= $key . ': ';
  138. if (is_array($value)) {
  139. foreach ($value as $_key => $_value)
  140. $header .= (is_string($_key) ? $_key . ' ' . $_value : $_value) . ',';
  141. $header = trim($header, ',');
  142. } else
  143. $header .= $value;
  144. $header .= self::CRLF;
  145. }
  146. return $header . self::CRLF;
  147. }
  148. /**
  149. * 创建邮件消息体
  150. *
  151. * @return string
  152. */
  153. public function createBody() {
  154. $body = '';
  155. switch ($this->contentType) {
  156. case self::MIME_TEXT:
  157. $body = $this->_encode($this->bodyText) . self::CRLF;
  158. break;
  159. case self::MIME_HTML:
  160. $body = $this->_encode($this->bodyHtml) . self::CRLF;
  161. break;
  162. case self::MIME_ALTERNATIVE:
  163. $body = $this->_createBoundary($this->_boundary(), 'text/plain');
  164. $body .= $this->_encode($this->bodyText) . self::CRLF;
  165. $body .= $this->_createBoundary($this->_boundary(), 'text/html');
  166. $body .= $this->_encode($this->bodyHtml) . self::CRLF;
  167. $body .= $this->_boundaryEnd($this->_boundary());
  168. break;
  169. default:
  170. $body .= $this->_boundaryStart($this->_boundary());
  171. $body .= sprintf("Content-Type: %s;%s" . "\tboundary=\"%s\"%s",
  172. 'multipart/alternative', self::CRLF, $this->_boundary(1),
  173. self::CRLF . self::CRLF);
  174. $body .= $this->_createBoundary($this->_boundary(1), 'text/plain') . self::CRLF;
  175. $body .= $this->_encode($this->bodyText) . self::CRLF . self::CRLF;
  176. $body .= $this->_createBoundary($this->_boundary(1), 'text/html') . self::CRLF;
  177. $body .= $this->_encode($this->bodyHtml) . self::CRLF . self::CRLF;
  178. $body .= $this->_boundaryEnd($this->_boundary(1));
  179. $body .= $this->_attach();
  180. break;
  181. }
  182. return $body;
  183. }
  184. /**
  185. * 设置发件人
  186. *
  187. * @param string $email 发件人邮箱
  188. * @param string $name 发件人姓名
  189. * @return void
  190. */
  191. public function setFrom($email, $name = null) {
  192. if (!$email || !is_string($email)) return;
  193. $this->from = $email;
  194. $name && $email = $this->_encodeHeader($name) . ' <' . $email . '>';
  195. $this->setMailHeader(self::FROM, $email, false);
  196. }
  197. /**
  198. * 取得发件人
  199. *
  200. * @return string
  201. */
  202. public function getFrom() {
  203. return $this->from;
  204. }
  205. /**
  206. * 设置收件人
  207. *
  208. * @param string|array $email 收件人邮箱
  209. * @param string $name 收件人姓名
  210. */
  211. public function setTo($email, $name = null) {
  212. if (!$email) return;
  213. $email = $this->_setRecipientMail($email, $name);
  214. $this->setMailHeader(self::TO, $email);
  215. }
  216. /**
  217. * 取得收件人
  218. *
  219. * @return array
  220. */
  221. public function getTo() {
  222. return $this->getMailHeader(self::TO);
  223. }
  224. /**
  225. * 设置抄送人
  226. *
  227. * @param string $email 抄送人邮箱
  228. * @param string $name 抄送人姓名
  229. */
  230. public function setCc($email, $name = null) {
  231. if (!$email) return;
  232. $email = $this->_setRecipientMail($email, $name);
  233. $this->setMailHeader(self::CC, $email);
  234. }
  235. /**
  236. * 取得抄送的对象
  237. *
  238. * @return array
  239. */
  240. public function getCc() {
  241. return $this->getMailHeader(self::CC);
  242. }
  243. /**
  244. * 设置暗送人
  245. *
  246. * @param string $email 暗送人邮箱
  247. * @param string $name 暗送人姓名
  248. */
  249. public function setBcc($email, $name = null) {
  250. if (!$email) return;
  251. $email = $this->_setRecipientMail($email, $name);
  252. $this->setMailHeader(self::BCC, $email);
  253. }
  254. /**
  255. * 取得暗送对象
  256. *
  257. * @return array
  258. */
  259. public function getBcc() {
  260. return $this->getMailHeader(self::BCC);
  261. }
  262. /**
  263. * 设置邮件主题
  264. *
  265. * @param string $subject 主题
  266. */
  267. public function setSubject($subject) {
  268. $this->setMailHeader(self::SUBJECT, $this->_encodeHeader($subject), false);
  269. }
  270. /**
  271. * 取得邮件主题
  272. *
  273. * @return string
  274. */
  275. public function getSubject() {
  276. $subject = $this->getMailHeader(self::SUBJECT);
  277. is_array($subject) && $subject = $subject[0];
  278. return str_replace(array("\r", "\n"), array('', ' '), $subject);
  279. }
  280. /**
  281. * 设置邮件日期
  282. *
  283. * @param string $data
  284. */
  285. public function setDate($date) {
  286. $this->setMailHeader(self::DATE, $date);
  287. }
  288. /**
  289. * 设置邮件头
  290. *
  291. * @param string $name 邮件头名称
  292. * @param string $value 邮件头对应的值
  293. * @param boolean $append 是否是追加
  294. * @return void
  295. */
  296. public function setMailHeader($name, $value, $append = true) {
  297. is_array($value) || $value = array($value);
  298. if (false === $append || !isset($this->mailHeader[$name])) {
  299. $this->mailHeader[$name] = $value;
  300. } else {
  301. foreach ($value as $key => $_value) {
  302. if (is_string($key))
  303. $this->mailHeader[$name][$key] = $_value;
  304. else
  305. $this->mailHeader[$name][] = $_value;
  306. }
  307. }
  308. }
  309. /**
  310. * 返回邮件头信息值
  311. *
  312. * @param string $name
  313. */
  314. public function getMailHeader($name) {
  315. if (!$name) return $this->mailHeader;
  316. return isset($this->mailHeader[$name]) ? $this->mailHeader[$name] : array();
  317. }
  318. /**
  319. * 设置邮件消息ID
  320. */
  321. public function setMessageId() {
  322. $user = array_pop($this->getFrom());
  323. $user || $user = getmypid();
  324. if ($recipient = $this->getRecipients()) {
  325. $recipient = array_rand($recipient);
  326. } else
  327. $recipient = 'No recipient';
  328. $host = isset($_SERVER["SERVER_NAME"]) ? $_SERVER["SERVER_NAME"] : php_uname('n');
  329. $message = sha1(time() . $user . mt_rand() . $recipient) . '@' . $host;
  330. $this->setMailHeader(self::MESSAGEID, '<' . $message . '>');
  331. }
  332. /**
  333. * 设置邮件编码
  334. *
  335. * @param string $encode
  336. */
  337. public function setContentEncode($encode = self::ENCODE_BASE64) {
  338. $this->encode = $encode;
  339. $this->setMailHeader(self::CONTENTENCODE, $encode);
  340. }
  341. /**
  342. * 设置邮件类型
  343. *
  344. * @param string $type
  345. */
  346. public function setContentType($type = self::MIME_TEXT) {
  347. if (self::MIME_TEXT == $type || self::MIME_HTML == $type)
  348. $contentType = sprintf("%s; charset=\"%s\"", $type, $this->charset);
  349. elseif (self::MIME_RELATED == $type)
  350. $contentType = sprintf("%s;%s type=\"text/html\";%s boundary=\"%s\"",
  351. self::MIME_RELATED, self::CRLF, self::CRLF, $this->_boundary());
  352. else
  353. $contentType = sprintf("%s;%s boundary=\"%s\"", $type, self::CRLF, $this->_boundary());
  354. $this->contentType = $type;
  355. $this->setMailHeader(self::CONTENTTYPE, $contentType, false);
  356. }
  357. /**
  358. * 上传附件
  359. *
  360. * @return string
  361. */
  362. private function _attach() {
  363. $attach = '';
  364. foreach ($this->attachment as $key => $value) {
  365. list($stream, $mime, $disposition, $encode, $filename, $cid) = $value;
  366. $filename || $filename = 'attachment_' . $key;
  367. $attach .= $this->_boundaryStart($this->_boundary());
  368. $attach .= sprintf(self::CONTENTTYPE . ": %s; name=\"%s\"%s", $mime, $filename,
  369. self::CRLF);
  370. $attach .= sprintf(self::CONTENTENCODE . ": %s%s", $encode, self::CRLF);
  371. if ($disposition == 'inline') {
  372. $attach .= sprintf(self::CONTENTID . ": <%s>%s", $cid, self::CRLF);
  373. }
  374. $attach .= sprintf(self::CONTENTPOSITION . ": %s; filename=\"%s\"%s%s", $disposition,
  375. $filename, self::CRLF, self::CRLF);
  376. $attach .= $this->_encode($stream, $encode) . self::CRLF;
  377. }
  378. $attach .= $this->_boundaryEnd($this->_boundary());
  379. return $attach;
  380. }
  381. /**
  382. * 取得下一个quoted-printable
  383. *
  384. * @param string $string
  385. * @return string
  386. */
  387. private static function getNextQpToken($string) {
  388. return '=' == substr($string, 0, 1) ? substr($string, 0, 3) : substr($string, 0, 1);
  389. }
  390. /**
  391. * 获取边界线
  392. *
  393. * @return string
  394. */
  395. private function _createBoundary($boundary, $contentType, $charset = '', $encode = '') {
  396. $result = '';
  397. $charset || $charset = $this->charset;
  398. $encode || $encode = $this->encode;
  399. $result .= $this->_boundaryStart($boundary);
  400. $result .= sprintf(self::CONTENTTYPE . ": %s; charset=\"%s\"", $contentType, $charset);
  401. $result .= self::CRLF;
  402. $result .= sprintf(self::CONTENTENCODE . ": %s%s", $encode, self::CRLF);
  403. $result .= self::CRLF;
  404. return $result;
  405. }
  406. /**
  407. *
  408. * @param boundary
  409. * @return string
  410. */
  411. private function _boundaryStart($boundary) {
  412. return '--' . $boundary . self::CRLF;
  413. }
  414. /**
  415. * 获取结束边界线
  416. *
  417. * @return string
  418. */
  419. private function _boundaryEnd($boundary) {
  420. return self::CRLF . '--' . $boundary . '--' . self::CRLF;
  421. }
  422. /**
  423. * 设置并返回边界线
  424. *
  425. * @param int $i 默认值为0
  426. * @return string
  427. */
  428. private function _boundary($i = 0) {
  429. if (!$this->boundary) {
  430. $uniq_id = md5(uniqid(time()));
  431. $this->boundary[0] = 'b1_' . $uniq_id;
  432. $this->boundary[1] = 'b2_' . $uniq_id;
  433. }
  434. return $i == 1 ? $this->boundary[1] : $this->boundary[0];
  435. }
  436. /**
  437. * 编码邮件内容
  438. *
  439. * @param string $message
  440. * @param string $encode
  441. * @return string
  442. */
  443. private function _encode($message, $encode = '') {
  444. return $this->_getEncoder($encode)->encode(trim($message), self::LINELENGTH, self::CRLF);
  445. }
  446. /**
  447. * 编码邮件头部
  448. *
  449. * @param string $message
  450. * @param string $encode
  451. * @return string
  452. */
  453. private function _encodeHeader($message, $encode = '') {
  454. $message = strtr(trim($message), array("\r" => '', "\n" => '', "\r\n" => ''));
  455. return $this->_getEncoder($encode)->encodeHeader($message, $this->charset, self::LINELENGTH,
  456. self::CRLF);
  457. }
  458. /**
  459. * 根据当前编码获取邮件编码器,并返回邮件编码器对象
  460. *
  461. * @param encode
  462. * @return IWindMailEncoder
  463. */
  464. private function _getEncoder($encode) {
  465. $encode || $encode = $this->encode;
  466. switch ($encode) {
  467. case self::ENCODE_QP:
  468. $mailEncoder = Wind::import("WIND:mail.encode.WindMailQp");
  469. break;
  470. case self::ENCODE_BASE64:
  471. $mailEncoder = Wind::import("WIND:mail.encode.WindMailBase64");
  472. break;
  473. case self::ENCODE_7BIT:
  474. case self::ENCODE_8BIT:
  475. default:
  476. $mailEncoder = Wind::import("WIND:mail.encode.WindMailBinary");
  477. break;
  478. }
  479. if (!class_exists($mailEncoder)) throw new WindMailException(
  480. '[mail.WindMail._encode] encod class for ' . $encode . ' is not exist.');
  481. return new $mailEncoder();
  482. }
  483. /**
  484. *
  485. * @param string $email
  486. * @param string $name
  487. */
  488. private function _setRecipientMail($email, $name) {
  489. $_email = '';
  490. if (is_array($email)) {
  491. foreach ($email as $_e => $_n) {
  492. $_email .= $_n ? $this->_encodeHeader($_n) . ' <' . $_e . '>' : $_e;
  493. $this->recipients[] = $_e;
  494. }
  495. } else {
  496. $_email = $name ? $this->_encodeHeader($name) . ' <' . $email . '>' : $email;
  497. $this->recipients[] = $email;
  498. }
  499. return $_email;
  500. }
  501. /**
  502. * 取得真实的收件人
  503. *
  504. * @return array
  505. */
  506. public function getRecipients() {
  507. return $this->recipients;
  508. }
  509. /**
  510. * 设置附件
  511. *
  512. * @param string $stream 附件名或者附件内容
  513. * @param string $mime 附件类型
  514. * @param string $disposition 附件展现方式
  515. * @param string $encode 附件编码
  516. * @param string $filename 文件名
  517. * @param string $cid 内容ID
  518. */
  519. public function setAttachment($stream, $mime = self::MIME_OCTETSTREAM, $disposition = self::DIS_ATTACHMENT, $encode = self::ENCODE_BASE64, $filename = null, $cid = 0) {
  520. $this->attachment[] = array($stream, $mime, $disposition, $encode, $filename, $cid);
  521. }
  522. /**
  523. * 设置邮件展示内容
  524. *
  525. * @param string $body
  526. */
  527. public function setBody($body) {
  528. $this->bodyHtml = $body;
  529. }
  530. /**
  531. * 设置邮件文本展示内容
  532. *
  533. * @param string $bodyText
  534. */
  535. public function setBodyText($bodyText) {
  536. $this->bodyText = $bodyText;
  537. }
  538. /**
  539. * 设置邮件字符
  540. *
  541. * @param string $charset
  542. */
  543. public function setCharset($charset) {
  544. $this->charset = $charset;
  545. }
  546. /**
  547. * 设置是否是内嵌资源
  548. *
  549. * @param boolean $embed
  550. */
  551. public function setEmbed($embed = false) {
  552. $this->embed = $embed;
  553. }
  554. }