123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545 |
- <?php
- /**
- * @copyright (C)2016-2099 Hnaoyun Inc.
- * @author XingMeng
- * @email hnxsh@foxmail.com
- * @date 2018年3月21日
- * 邮件发送类
- */
- namespace core\basic;
-
- class Smtp
- {
-
- // 邮件传输代理服务器地址
- protected $sendServer;
-
- // 邮件传输代理服务器端口
- protected $port;
-
- // 是否是安全连接
- protected $isSecurity;
-
- // 邮件传输代理用户名
- protected $userName;
-
- // 邮件传输代理密码
- protected $password;
-
- // 发件人
- protected $from;
-
- // 收件人
- protected $to = array();
-
- // 抄送
- protected $cc = array();
-
- // 秘密抄送
- protected $bcc = array();
-
- // 主题
- protected $subject;
-
- // 邮件正文
- protected $body;
-
- // 附件
- protected $attachment = array();
-
- // 调试模式
- protected $debug;
-
- // 错误信息
- protected $errorMessage;
-
- // 资源句柄
- protected $socket;
-
- /**
- * 设置邮件传输代理,默认为安全链接
- *
- * @param string $server
- * 代理服务器的ip或者域名
- * @param string $username
- * 认证账号
- * @param string $password
- * 认证密码
- * @param int $port
- * 代理服务器的端口,smtp默认25号端口
- * @param boolean $isSecurity
- * 到服务器的连接是否为安全连接,默认false
- * @return boolean
- */
- public function __construct($server = "", $username = "", $password = "", $port = 465, $isSecurity = true, $debug = false)
- {
- if ($server) {
- $this->sendServer = $server;
- $this->port = $port;
- $this->isSecurity = $isSecurity ? true : false;
- $this->debug = $debug;
- $this->userName = empty($username) ? "" : base64_encode($username);
- $this->password = empty($password) ? "" : base64_encode($password);
- $this->from = $username;
- } else {
- $smtp = Config::get();
- $this->sendServer = $smtp['smtp_server'];
- $this->port = $smtp['smtp_port'];
- $this->isSecurity = $smtp['smtp_ssl'] ? true : false;
- $this->debug = $debug;
- $this->userName = base64_encode($smtp['smtp_username']);
- $this->password = base64_encode($smtp['smtp_password']);
- $this->from = $smtp['smtp_username'];
- }
- return true;
- }
-
- /**
- * 设置收件人,多个收件人,调用多次或用逗号隔开.
- *
- * @param string $to
- * 收件人地址
- * @return boolean
- */
- public function setReceiver($to)
- {
- if (strpos($to, ',')) {
- $this->to = explode(',', $to);
- } else {
- $this->to[] = $to;
- }
- return true;
- }
-
- /**
- * 设置抄送,多个抄送,调用多次或用逗号隔开.
- *
- * @param string $cc
- * 抄送地址
- * @return boolean
- */
- public function setCc($cc)
- {
- if (strpos($cc, ',')) {
- $this->cc = explode(',', $cc);
- } else {
- $this->cc[] = $cc;
- }
- return true;
- }
-
- /**
- * 设置秘密抄送,多个秘密抄送,调用多次或用逗号隔开.
- *
- * @param string $bcc
- * 秘密抄送地址
- * @return boolean
- */
- public function setBcc($bcc)
- {
- if (strpos($bcc, ',')) {
- $this->bcc = explode(',', $bcc);
- } else {
- $this->bcc[] = $bcc;
- }
- return true;
- }
-
- /**
- * 设置邮件附件,多个附件,调用多次
- *
- * @param string $file
- * 文件地址
- * @return boolean
- */
- public function addAttachment($file)
- {
- if (! file_exists($file)) {
- $this->errorMessage = "file " . $file . " does not exist.";
- return false;
- }
- $this->attachment[] = $file;
- return true;
- }
-
- /**
- * 设置邮件信息
- *
- * @param string $body
- * 邮件主题
- * @param string $subject
- * 邮件主体内容,可以是纯文本,也可是是HTML文本
- * @return boolean
- */
- public function setMail($subject, $body)
- {
- $this->subject = base64_encode($subject);
- $this->body = base64_encode($body);
- return true;
- }
-
- /**
- * 发送邮件
- *
- * @return boolean
- */
- public function sendMail($to = '', $subject = '', $body = '')
- {
- if ($to) {
- $this->setReceiver($to);
- $this->setMail($subject, $body);
- }
- $command = $this->getCommand();
- if (! $this->socket($this->isSecurity)) {
- return false;
- }
- foreach ($command as $value) {
- $result = $this->sendCommand($value[0], $value[1]);
- if ($result) {
- continue;
- } else {
- return false;
- }
- }
- // 关闭连接
- $this->close();
- return true;
- }
-
- /**
- * 返回错误信息
- *
- * @return string
- */
- public function error()
- {
- if (! isset($this->errorMessage)) {
- $this->errorMessage = "";
- }
- return $this->errorMessage;
- }
-
- /**
- * 返回mail命令
- *
- * @access protected
- * @return array
- */
- protected function getCommand()
- {
- $separator = "----=_Part_" . md5($this->from . time()) . uniqid(); // 分隔符
- $command = array(
- array(
- "HELO sendmail\r\n",
- 250
- )
- );
- if (! empty($this->userName)) {
- $command[] = array(
- "AUTH LOGIN\r\n",
- 334
- );
- $command[] = array(
- $this->userName . "\r\n",
- 334
- );
- $command[] = array(
- $this->password . "\r\n",
- 235
- );
- }
- // 设置发件人
- $command[] = array(
- "MAIL FROM: <" . $this->from . ">\r\n",
- 250
- );
- $header = "FROM: <" . $this->from . ">\r\n";
- // 设置收件人
- if (! empty($this->to)) {
- $count = count($this->to);
- if ($count == 1) {
- $command[] = array(
- "RCPT TO: <" . $this->to[0] . ">\r\n",
- 250
- );
- $header .= "TO: <" . $this->to[0] . ">\r\n";
- } else {
- for ($i = 0; $i < $count; $i ++) {
- $command[] = array(
- "RCPT TO: <" . $this->to[$i] . ">\r\n",
- 250
- );
- if ($i == 0) {
- $header .= "TO: <" . $this->to[$i] . ">";
- } elseif ($i + 1 == $count) {
- $header .= ",<" . $this->to[$i] . ">\r\n";
- } else {
- $header .= ",<" . $this->to[$i] . ">";
- }
- }
- }
- }
- // 设置抄送
- if (! empty($this->cc)) {
- $count = count($this->cc);
- if ($count == 1) {
- $command[] = array(
- "RCPT TO: <" . $this->cc[0] . ">\r\n",
- 250
- );
- $header .= "CC: <" . $this->cc[0] . ">\r\n";
- } else {
- for ($i = 0; $i < $count; $i ++) {
- $command[] = array(
- "RCPT TO: <" . $this->cc[$i] . ">\r\n",
- 250
- );
- if ($i == 0) {
- $header .= "CC: <" . $this->cc[$i] . ">";
- } elseif ($i + 1 == $count) {
- $header .= ",<" . $this->cc[$i] . ">\r\n";
- } else {
- $header .= ",<" . $this->cc[$i] . ">";
- }
- }
- }
- }
- // 设置秘密抄送
- if (! empty($this->bcc)) {
- $count = count($this->bcc);
- if ($count == 1) {
- $command[] = array(
- "RCPT TO: <" . $this->bcc[0] . ">\r\n",
- 250
- );
- $header .= "BCC: <" . $this->bcc[0] . ">\r\n";
- } else {
- for ($i = 0; $i < $count; $i ++) {
- $command[] = array(
- "RCPT TO: <" . $this->bcc[$i] . ">\r\n",
- 250
- );
- if ($i == 0) {
- $header .= "BCC: <" . $this->bcc[$i] . ">";
- } elseif ($i + 1 == $count) {
- $header .= ",<" . $this->bcc[$i] . ">\r\n";
- } else {
- $header .= ",<" . $this->bcc[$i] . ">";
- }
- }
- }
- }
- // 主题
- $header .= "Subject: =?UTF-8?B?" . $this->subject . "?=\r\n";
- if (isset($this->attachment)) {
- // 含有附件的邮件头需要声明成这个
- $header .= "Content-Type: multipart/mixed;\r\n";
- } elseif (false) {
- // 邮件体含有图片资源的,且包含的图片在邮件内部时声明成这个,如果是引用的远程图片,就不需要了
- $header .= "Content-Type: multipart/related;\r\n";
- } else {
- // html或者纯文本的邮件声明成这个
- $header .= "Content-Type: multipart/alternative;\r\n";
- }
- // 邮件头分隔符
- $header .= "\t" . 'boundary="' . $separator . '"';
- $header .= "\r\nMIME-Version: 1.0\r\n";
- // 这里开始是邮件的body部分,body部分分成几段发送
- $header .= "\r\n--" . $separator . "\r\n";
- $header .= "Content-Type:text/html; charset=utf-8\r\n";
- $header .= "Content-Transfer-Encoding: base64\r\n\r\n";
- $header .= $this->body . "\r\n";
- $header .= "--" . $separator . "\r\n";
- // 加入附件
- if (! empty($this->attachment)) {
- $count = count($this->attachment);
- for ($i = 0; $i < $count; $i ++) {
- $header .= "\r\n--" . $separator . "\r\n";
- $header .= "Content-Type: " . $this->getMIMEType($this->attachment[$i]) . '; name="=?UTF-8?B?' . base64_encode(basename($this->attachment[$i])) . '?="' . "\r\n";
- $header .= "Content-Transfer-Encoding: base64\r\n";
- $header .= 'Content-Disposition: attachment; filename="=?UTF-8?B?' . base64_encode(basename($this->attachment[$i])) . '?="' . "\r\n";
- $header .= "\r\n";
- $header .= $this->readFile($this->attachment[$i]);
- $header .= "\r\n--" . $separator . "\r\n";
- }
- }
- // 结束邮件数据发送
- $header .= "\r\n.\r\n";
-
- $command[] = array(
- "DATA\r\n",
- 354
- );
- $command[] = array(
- $header,
- 250
- );
- $command[] = array(
- "QUIT\r\n",
- 221
- );
- return $command;
- }
-
- /**
- * 发送命令
- *
- * @param string $command
- * 发送到服务器的smtp命令
- * @param int $code
- * 期望服务器返回的响应吗
- * @return boolean
- */
- protected function sendCommand($command, $code)
- {
- if ($this->debug) {
- echo 'Send command:' . $command . ',expected code:' . $code . '<br />';
- }
- try {
- if (fwrite($this->socket, $command)) {
- // 当邮件内容分多次发送时,没有$code,服务器没有返回
- if (empty($code)) {
- return true;
- }
- // 读取服务器返回
- $data = trim(fread($this->socket, 1024));
- if (! mb_check_encoding($data, 'utf-8')) {
- $data = iconv('gbk', 'utf-8', $data);
- }
- if ($this->debug) {
- echo 'response:' . $data . '<br /><br />';
- }
- if ($data) {
- $pattern = "/^" . $code . "+?/";
- if (preg_match($pattern, $data)) {
- return true;
- } else {
- $this->errorMessage = $data;
- return false;
- }
- } else {
- return false;
- }
- } else {
- $this->errorMessage = "Error: " . $command . " send failed";
- return false;
- }
- } catch (\Exception $e) {
- $this->errorMessage = "Error:" . $e->getMessage();
- }
- }
-
- /**
- * 读取附件文件内容,返回base64编码后的文件内容
- *
- * @param string $file
- * 文件
- * @return mixed
- */
- protected function readFile($file)
- {
- if (file_exists($file)) {
- $file_obj = file_get_contents($file);
- return base64_encode($file_obj);
- } else {
- $this->errorMessage = "file " . $file . " dose not exist";
- return false;
- }
- }
-
- /**
- * 获取附件MIME类型
- *
- * @param string $file
- * 文件
- * @return mixed
- */
- protected function getMIMEType($file)
- {
- if (file_exists($file)) {
- $mime = mime_content_type($file);
- return $mime;
- } else {
- return false;
- }
- }
-
- /**
- * 建立到服务器的网络连接
- *
- * @return boolean
- */
- protected function socket($ssl = true)
- {
- if ($ssl && ! extension_loaded('openssl')) {
- $this->errorMessage = '服务器未启用openssl扩展,无法使用加密方式发送邮件!';
- return false;
- }
-
- if (function_exists('stream_socket_client')) {
-
- if (! function_exists('stream_socket_enable_crypto')) {
- $this->errorMessage = '服务器已经禁用stream_socket_enable_crypto函数,无法发送邮件!';
- return false;
- }
-
- if (! function_exists('stream_set_blocking')) {
- $this->errorMessage = '服务器已经禁用stream_set_blocking函数,无法发送邮件!';
- return false;
- }
-
- // 建立连接
- $remoteAddr = "tcp://" . $this->sendServer . ":" . $this->port;
- $this->socket = stream_socket_client($remoteAddr, $errno, $errstr, 30);
- if (! $this->socket) {
- $this->errorMessage = $errstr;
- return false;
- }
-
- // 设置加密方式
- $crypto_method = STREAM_CRYPTO_METHOD_TLS_CLIENT;
- if (defined('STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT')) {
- $crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT;
- $crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT;
- }
- stream_socket_enable_crypto($this->socket, $ssl, $crypto_method);
- } elseif (function_exists('fsockopen')) {
- if ($ssl) {
- $remoteAddr = "ssl://" . $this->sendServer;
- } else {
- $remoteAddr = "tcp://" . $this->sendServer;
- }
- $this->socket = fsockopen($remoteAddr, $this->port, $errno, $errstr, 30);
- if (! $this->socket) {
- $this->errorMessage = $errstr;
- return false;
- }
- } else {
- $this->errorMessage = '服务器已经禁用stream_socket_client和fsockopen函数,请至少开启一个才能发送邮件!';
- return false;
- }
-
- stream_set_blocking($this->socket, 1); // 设置阻塞模式
- $str = fread($this->socket, 1024);
- if (! preg_match("/220+?/", $str)) {
- $this->errorMessage = $str;
- return false;
- }
- return true;
- }
-
- /**
- * 关闭安全socket
- *
- * @return boolean
- */
- protected function close()
- {
- if (isset($this->socket) && is_object($this->socket)) {
- stream_socket_shutdown($this->socket, STREAM_SHUT_WR);
- return true;
- }
- $this->errorMessage = "No resource can to be close";
- return false;
- }
- }
|