545 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			545 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
<?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;
 | 
						||
    }
 | 
						||
} |