PHP邮件发送类【完整版】

jerry thinkphp 2015年11月19日 收藏
支持发送纯文本邮件和HTML格式的邮件,可以多收件人,多抄送,多秘密抄送,带附件的邮件.
  1. <?php
  2. /**
  3. * 邮件发送类
  4. * 支持发送纯文本邮件和HTML格式的邮件,可以多收件人,多抄送,多秘密抄送,带附件的邮件
  5. * 需要的php扩展,sockets和Fileinfo。
  6. * @example
  7. * $mail = new MySendMail();
  8. * $mail->setServer("XXXXX", "XXXXX@XXXXX", "XXXXX"); 设置smtp服务器
  9. * $mail->setFrom("XXXXX"); 设置发件人
  10. * $mail->setReceiver("XXXXX"); 设置收件人,多个收件人,调用多次
  11. * $mail->setCc("XXXX"); 设置抄送,多个抄送,调用多次
  12. * $mail->setBcc("XXXXX"); 设置秘密抄送,多个秘密抄送,调用多次
  13. * $mail->setMailInfo("test", "<b>test</b>"); 设置邮件主题、内容
  14. * $mail->sendMail(); 发送
  15. */
  16. class MySendMail {
  17.     /**
  18.     * @var string 邮件传输代理用户名
  19.     * @access private
  20.     */
  21.     private $_userName;

  22.     /**
  23.     * @var string 邮件传输代理密码
  24.     * @access private
  25.     */
  26.     private $_password;

  27.     /**
  28.     * @var string 邮件传输代理服务器地址
  29.     * @access protected
  30.     */
  31.     protected $_sendServer;

  32.     /**
  33.     * @var int 邮件传输代理服务器端口
  34.     * @access protected
  35.     */
  36.     protected $_port=25;

  37.     /**
  38.     * @var string 发件人
  39.     * @access protected
  40.     */
  41.     protected $_from;

  42.     /**
  43.     * @var string 收件人
  44.     * @access protected
  45.     */
  46.     protected $_to;

  47.     /**
  48.     * @var string 抄送
  49.     * @access protected
  50.     */
  51.     protected $_cc;

  52.     /**
  53.     * @var string 秘密抄送
  54.     * @access protected
  55.     */
  56.     protected $_bcc;

  57.     /**
  58.     * @var string 主题
  59.     * @access protected
  60.     */
  61.     protected $_subject;

  62.     /**
  63.     * @var string 邮件正文
  64.     * @access protected
  65.     */
  66.     protected $_body;

  67.     /**
  68.     * @var string 附件
  69.     * @access protected
  70.     */
  71.     protected $_attachment;

  72.     /**
  73.     * @var reource socket资源
  74.     * @access protected
  75.     */
  76.     protected $_socket;

  77.     /**
  78.     * @var string 错误信息
  79.     * @access protected
  80.     */
  81.     protected $_errorMessage;

  82.     /**
  83.     * 设置邮件传输代理,如果是可以匿名发送有邮件的服务器,只需传递代理服务器地址就行
  84.     * @access public
  85.     * @param string $server 代理服务器的ip或者域名
  86.     * @param string $username 认证账号
  87.     * @param string $password 认证密码
  88.     * @param int $port 代理服务器的端口,smtp默认25号端口
  89.     * @return boolean
  90.     */
  91.     public function setServer($server, $username="", $password="", $port=25) {
  92.         $this->_sendServer = $server;
  93.         $this->_port = $port;
  94.         if(!empty($username)) {
  95.             $this->_userName = base64_encode($username);
  96.         }
  97.         if(!empty($password)) {
  98.             $this->_password = base64_encode($password);
  99.         }
  100.         return true;
  101.     }

  102.     /**
  103.     * 设置发件人
  104.     * @access public
  105.     * @param string $from 发件人地址
  106.     * @return boolean
  107.     */
  108.     public function setFrom($from) {
  109.         $this->_from = $from;
  110.         return true;
  111.     }

  112.     /**
  113.     * 设置收件人,多个收件人,连续调用多次.
  114.     * @access public
  115.     * @param string $to 收件人地址
  116.     * @return boolean
  117.     */
  118.     public function setReceiver($to) {
  119.         if(isset($this->_to)) {
  120.             if(is_string($this->_to)) {
  121.                 $this->_to = array($this->_to);
  122.                 $this->_to[] = $to;
  123.                 return true;
  124.             }
  125.             elseif(is_array($this->_to)) {
  126.                 $this->_to[] = $to;
  127.                 return true;
  128.             }
  129.             else {
  130.                 return false;
  131.             }
  132.         }
  133.         else {
  134.             $this->_to = $to;
  135.             return true;
  136.         }
  137.     }

  138.     /**
  139.     * 设置抄送,多个抄送,连续调用多次.
  140.     * @access public
  141.     * @param string $cc 抄送地址
  142.     * @return boolean
  143.     */
  144.     public function setCc($cc) {
  145.         if(isset($this->_cc)) {
  146.             if(is_string($this->_cc)) {
  147.                 $this->_cc = array($this->_cc);
  148.                 $this->_cc[] = $cc;
  149.                 return true;
  150.             }
  151.             elseif(is_array($this->_cc)) {
  152.                 $this->_cc[] = $cc;
  153.                 return true;
  154.             }
  155.             else {
  156.                 return false;
  157.             }
  158.         }
  159.         else {
  160.             $this->_cc = $cc;
  161.             return true;
  162.         }
  163.     }

  164.     /**
  165.     * 设置秘密抄送,多个秘密抄送,连续调用多次
  166.     * @access public
  167.     * @param string $bcc 秘密抄送地址
  168.     * @return boolean
  169.     */
  170.     public function setBcc($bcc) {
  171.         if(isset($this->_bcc)) {
  172.             if(is_string($this->_bcc)) {
  173.                 $this->_bcc = array($this->_bcc);
  174.                 $this->_bcc[] = $bcc;
  175.                 return true;
  176.             }
  177.             elseif(is_array($this->_bcc)) {
  178.                 $this->_bcc[] = $bcc;
  179.                 return true;
  180.             }
  181.             else {
  182.                 return false;
  183.             }
  184.         }
  185.         else {
  186.             $this->_bcc = $bcc;
  187.             return true;
  188.         }
  189.     }

  190.     /**
  191.     * 设置邮件信息
  192.     * @access public
  193.     * @param string $body 邮件主题
  194.     * @param string $subject 邮件主体内容,可以是纯文本,也可是是HTML文本
  195.     * @param string $attachment 附件,文件地址
  196.     * @return boolean
  197.     */
  198.     public function setMailInfo($subject, $body, $attachment="") {
  199.         $this->_subject = $subject;
  200.         $this->_body = base64_encode($body);
  201.         if(!empty($attachment)) {
  202.             $this->_attachment = $attachment;
  203.         }
  204.         return true;
  205.     }

  206.     /**
  207.     * 发送邮件
  208.     * @access public
  209.     * @return boolean
  210.     */
  211.     public function sendMail() {
  212.         $command = $this->getCommand();
  213.         $this->socket();

  214.         foreach ($command as $value) {
  215.             if($this->sendCommand($value[0], $value[1])) {
  216.                 continue;
  217.             }
  218.             else{
  219.                 return false;
  220.             }
  221.         }

  222.         //其实这里也没必要关闭,smtp命令:QUIT发出之后,服务器就关闭了连接,本地的socket资源会自动释放
  223.         $this->close(); 
  224.         echo 'Mail OK!';
  225.         return true;
  226.     }

  227.     /**
  228.     * 返回错误信息
  229.     * @return string
  230.     */
  231.     public function error(){
  232.         if(!isset($this->_errorMessage)) {
  233.             $this->_errorMessage = "";
  234.         }
  235.         return $this->_errorMessage;
  236.     }

  237.     /**
  238.     * 返回mail命令
  239.     * @access protected
  240.     * @return array
  241.     */
  242.     protected function getCommand() {
  243.         $command = array(
  244.                 array("HELO sendmail\r\n", 250)
  245.             );
  246.         if(!empty($this->_userName)){
  247.             $command[] = array("AUTH LOGIN\r\n", 334);
  248.             $command[] = array($this->_userName . "\r\n", 334);
  249.             $command[] = array($this->_password . "\r\n", 235);
  250.         }
  251.         $command[] = array("MAIL FROM:<" . $this->_from . ">\r\n", 250);

  252.         $separator = "----=_Part_" . md5($this->_from . time()) . uniqid(); //分隔符
  253.         //设置发件人
  254.         $header = "FROM: test<" . $this->_from . ">\r\n";

  255.         //设置收件人
  256.         if(is_array($this->_to)) {
  257.             $count = count($this->_to);
  258.             for($i=0; $i<$count; $i++){
  259.                 $command[] = array("RCPT TO: <" . $this->_to[$i] . ">\r\n", 250);
  260.                 if($i == 0){
  261.                     $header .= "TO: <" . $this->_to[$i] .">";
  262.                 }
  263.                 elseif($i + 1 == $count){
  264.                     $header .= ",<" . $this->_to[$i] .">\r\n";
  265.                 }
  266.                 else{
  267.                     $header .= ",<" . $this->_to[$i] .">";
  268.                 }
  269.             }
  270.         }
  271.         else{
  272.             $command[] = array("RCPT TO: <" . $this->_to . ">\r\n", 250);
  273.             $header .= "TO: <" . $this->_to . ">\r\n";
  274.         }

  275.         //设置抄送
  276.         if(isset($this->_cc)) {
  277.             if(is_array($this->_cc)) {
  278.                 $count = count($this->_cc);
  279.                 for($i=0; $i<$count; $i++){
  280.                     $command[] = array("RCPT TO: <" . $this->_cc[$i] . ">\r\n", 250);
  281.                     if($i == 0){
  282.                     $header .= "CC: <" . $this->_cc[$i] .">";
  283.                     }
  284.                     elseif($i + 1 == $count){
  285.                         $header .= ",<" . $this->_cc[$i] .">\r\n";
  286.                     }
  287.                     else{
  288.                         $header .= ",<" . $this->_cc[$i] .">";
  289.                     }
  290.                 }
  291.             }
  292.             else{
  293.                 $command[] = array("RCPT TO: <" . $this->_cc . ">\r\n", 250);
  294.                 $header .= "CC: <" . $this->_cc . ">\r\n";
  295.             }
  296.         }

  297.         //设置秘密抄送
  298.         if(isset($this->_bcc)) {
  299.             if(is_array($this->_bcc)) {
  300.                 $count = count($this->_bcc);
  301.                 for($i=0; $i<$count; $i++){
  302.                     $command[] = array("RCPT TO: <" . $this->_bcc[$i] . ">\r\n", 250);
  303.                     if($i == 0){
  304.                     $header .= "BCC: <" . $this->_bcc[$i] .">";
  305.                     }
  306.                     elseif($i + 1 == $count){
  307.                         $header .= ",<" . $this->_bcc[$i] .">\r\n";
  308.                     }
  309.                     else{
  310.                         $header .= ",<" . $this->_bcc[$i] .">";
  311.                     }
  312.                 }
  313.             }
  314.             else{
  315.                 $command[] = array("RCPT TO: <" . $this->_bcc . ">\r\n", 250);
  316.                 $header .= "BCC: <" . $this->_bcc . ">\r\n";
  317.             }
  318.         }

  319.         $header .= "Subject: " . $this->_subject ."\r\n";
  320.         if(isset($this->_attachment)) {
  321.             //含有附件的邮件头需要声明成这个
  322.             $header .= "Content-Type: multipart/mixed;\r\n";
  323.         }
  324.         elseif(false){
  325.             //邮件体含有图片资源的需要声明成这个
  326.             $header .= "Content-Type: multipart/related;\r\n";
  327.         }
  328.         else{
  329.             //html或者纯文本的邮件声明成这个
  330.             $header .= "Content-Type: multipart/alternative;\r\n";
  331.         }

  332.         //邮件头分隔符
  333.         $header .= "\t" . 'boundary="' . $separator . '"';
  334.         $header .= "\r\nMIME-Version: 1.0\r\n";
  335.         $header .= "\r\n--" . $separator . "\r\n";
  336.         $header .= "Content-Type:text/html; charset=utf-8\r\n";
  337.         $header .= "Content-Transfer-Encoding: base64\r\n\r\n";
  338.         $header .= $this->_body . "\r\n";
  339.         $header .= "--" . $separator . "\r\n";

  340.         //加入附件
  341.         if(isset($this->_attachment)){
  342.             $header .= "\r\n--" . $separator . "\r\n";
  343.             $header .= "Content-Type: " . $this->getMIMEType() . '; name="' . basename($this->_attachment) . '"' . "\r\n";
  344.             $header .= "Content-Transfer-Encoding: base64\r\n";
  345.             $header .= 'Content-Disposition: attachment; filename="' . basename($this->_attachment) . '"' . "\r\n";
  346.             $header .= "\r\n";
  347.             $header .= $this->readFile();
  348.             $header .= "\r\n--" . $separator . "\r\n";
  349.         }

  350.         $header .= "\r\n.\r\n";

  351.         $command[] = array("DATA\r\n", 354);
  352.         $command[] = array($header, 250);
  353.         $command[] = array("QUIT\r\n", 221);

  354.         return $command;
  355.     }

  356.     /**
  357.     * 发送命令
  358.     * @access protected
  359.     * @param string $command 发送到服务器的smtp命令
  360.     * @param int $code 期望服务器返回的响应吗
  361.     * @return boolean
  362.     */
  363.     protected function sendCommand($command, $code) {
  364.         echo 'Send command:' . $command . ',expected code:' . $code . '<br />';
  365.         //发送命令给服务器
  366.         try{
  367.             if(socket_write($this->_socket, $command, strlen($command))){

  368.                 //当邮件内容分多次发送时,没有$code,服务器没有返回
  369.                 if(empty($code))  {
  370.                     return true;
  371.                 }

  372.                 //读取服务器返回
  373.                 $data = trim(socket_read($this->_socket, 1024));
  374.                 echo 'response:' . $data . '<br /><br />';

  375.                 if($data) {
  376.                     $pattern = "/^".$code."/";
  377.                     if(preg_match($pattern, $data)) {
  378.                         return true;
  379.                     }
  380.                     else{
  381.                         $this->_errorMessage = "Error:" . $data . "|**| command:";
  382.                         return false;
  383.                     }
  384.                 }
  385.                 else{
  386.                     $this->_errorMessage = "Error:" . socket_strerror(socket_last_error());
  387.                     return false;
  388.                 }
  389.             }
  390.             else{
  391.                 $this->_errorMessage = "Error:" . socket_strerror(socket_last_error());
  392.                 return false;
  393.             }
  394.         }catch(Exception $e) {
  395.             $this->_errorMessage = "Error:" . $e->getMessage();
  396.         }
  397.     }

  398.     /**
  399.     * 读取附件文件内容,返回base64编码后的文件内容
  400.     * @access protected
  401.     * @return mixed
  402.     */
  403.     protected function readFile() {
  404.         if(isset($this->_attachment) && file_exists($this->_attachment)) {
  405.             $file = file_get_contents($this->_attachment);
  406.             return base64_encode($file);
  407.         }
  408.         else {
  409.             return false;
  410.         }
  411.     }

  412.     /**
  413.     * 获取附件MIME类型
  414.     * @access protected
  415.     * @return mixed
  416.     */
  417.     protected function getMIMEType() {
  418.         if(isset($this->_attachment) && file_exists($this->_attachment)) {
  419.             $mime = mime_content_type($this->_attachment);
  420.             if(! preg_match("/gif|jpg|png|jpeg/", $mime)){
  421.                 $mime = "application/octet-stream";
  422.             }
  423.             return $mime;
  424.         }
  425.         else {
  426.             return false;
  427.         }
  428.     }

  429.     /**
  430.     * 建立到服务器的网络连接
  431.     * @access private
  432.     * @return boolean
  433.     */
  434.     private function socket() {
  435.         if(!function_exists("socket_create")) {
  436.             $this->_errorMessage = "Extension sockets must be enabled";
  437.             return false;
  438.         }
  439.         //创建socket资源
  440.         $this->_socket = socket_create(AF_INET, SOCK_STREAM, getprotobyname('tcp'));

  441.         if(!$this->_socket) {
  442.             $this->_errorMessage = socket_strerror(socket_last_error());
  443.             return false;
  444.         }

  445.         socket_set_block($this->_socket);//设置阻塞模式

  446.         //连接服务器
  447.         if(!socket_connect($this->_socket, $this->_sendServer, $this->_port)) {
  448.             $this->_errorMessage = socket_strerror(socket_last_error());
  449.             return false;
  450.         }
  451.         socket_read($this->_socket, 1024);

  452.         return true;
  453.     }

  454.     /**
  455.     * 关闭socket
  456.     * @access private
  457.     * @return boolean
  458.     */
  459.     private function close() {
  460.         if(isset($this->_socket) && is_object($this->_socket)) {
  461.             $this->_socket->close();
  462.             return true;
  463.         }
  464.         $this->_errorMessage = "No resource can to be close";
  465.         return false;
  466.     }
  467. }

  468. /**************************** Test ***********************************/
  469. $mail = new MySendMail();
  470. $mail->setServer("XXXXX", "XXXXX@XXXXX", "XXXXX");
  471. $mail->setFrom("XXXXX@XXXXX");
  472. $mail->setReceiver("XXXXX@XXXXX");
  473. //$mail->setReceiver("XXXXX@XXXXX");
  474. $mail->setCc("XXXXX@XXXXX");
  475. $mail->setCc("XXXXX@XXXXX");
  476. $mail->setBcc("XXXXX@XXXXX");
  477. $mail->setBcc("XXXXX@XXXXX");
  478. $mail->setBcc("XXXXX@XXXXX");
  479. $mail->setMailInfo("test", "<b>test</b>", "sms.zip");
  480. $mail->sendMail();