Browse Source

Update Core\Mail

To send email with multi recipients via Bcc headers.
Emails end up in spam :(
Visman 4 years ago
parent
commit
0fefe8a715
1 changed files with 135 additions and 135 deletions
  1. 135 135
      app/Core/Mail.php

+ 135 - 135
app/Core/Mail.php

@@ -17,6 +17,11 @@ class Mail
      */
      */
     protected $language;
     protected $language;
 
 
+    /**
+     * @var string
+     */
+    protected $from;
+
     /**
     /**
      * @var array
      * @var array
      */
      */
@@ -53,14 +58,10 @@ class Mail
     protected $auth = 0;
     protected $auth = 0;
 
 
     /**
     /**
-     * Конструктор
-     *
-     * @param mixed $host
-     * @param mixed $user
-     * @param mixed $pass
-     * @param mixed $ssl
-     * @param mixed $eol
+     * var int
      */
      */
+    protected $maxRecipients = 1;
+
     public function __construct($host, $user, $pass, $ssl, $eol)
     public function __construct($host, $user, $pass, $ssl, $eol)
     {
     {
         if (
         if (
@@ -68,6 +69,7 @@ class Mail
             && \strlen(\trim($host)) > 0
             && \strlen(\trim($host)) > 0
         ) {
         ) {
             list($host, $port) = \explode(':', $host);
             list($host, $port) = \explode(':', $host);
+
             if (
             if (
                 empty($port)
                 empty($port)
                 || $port < 1
                 || $port < 1
@@ -75,6 +77,7 @@ class Mail
             ) {
             ) {
                 $port = 25;
                 $port = 25;
             }
             }
+
             $this->smtp = [
             $this->smtp = [
                 'host' => ($ssl ? 'ssl://' : '') . $host,
                 'host' => ($ssl ? 'ssl://' : '') . $host,
                 'port' => (int) $port,
                 'port' => (int) $port,
@@ -83,17 +86,13 @@ class Mail
             ];
             ];
             $this->EOL = "\r\n";
             $this->EOL = "\r\n";
         } else {
         } else {
-            $this->EOL = \in_array($eol, ["\r\n", "\n", "\r"]) ? $eol : PHP_EOL;
+            $this->EOL = \in_array($eol, ["\r\n", "\n", "\r"]) ? $eol : \PHP_EOL;
         }
         }
     }
     }
 
 
     /**
     /**
      * Валидация email
      * Валидация email
      *
      *
-     * @param mixed $email
-     * @param bool $strict
-     * @param bool $idna
-     *
      * @return false|string
      * @return false|string
      */
      */
     public function valid($email, bool $strict = false, bool $idna = false)
     public function valid($email, bool $strict = false, bool $idna = false)
@@ -156,6 +155,7 @@ class Mail
             } else {
             } else {
                 $mx = @\dns_get_record($domainASCII, \DNS_MX);
                 $mx = @\dns_get_record($domainASCII, \DNS_MX);
             }
             }
+
             if (empty($mx)) {
             if (empty($mx)) {
                 return false;
                 return false;
             }
             }
@@ -164,14 +164,22 @@ class Mail
         return $local . '@' . ($idna ? $domainASCII : $domain);
         return $local . '@' . ($idna ? $domainASCII : $domain);
     }
     }
 
 
+    /**
+     * Устанавливает максимальное кол-во получателей в одном письме
+     */
+    public function setMaxRecipients(int $max): Mail
+    {
+        $this->maxRecipients = $max;
+
+        return $this;
+    }
+
     /**
     /**
      * Сброс
      * Сброс
-     *
-     * @return Mail
      */
      */
     public function reset(): Mail
     public function reset(): Mail
     {
     {
-        $this->to = [];
+        $this->to      = [];
         $this->headers = [];
         $this->headers = [];
         $this->message = null;
         $this->message = null;
 
 
@@ -180,10 +188,6 @@ class Mail
 
 
     /**
     /**
      * Задает тему письма
      * Задает тему письма
-     *
-     * @param string $subject
-     *
-     * @return Mail
      */
      */
     public function setSubject(string $subject): Mail
     public function setSubject(string $subject): Mail
     {
     {
@@ -196,19 +200,18 @@ class Mail
      * Добавляет заголовок To
      * Добавляет заголовок To
      *
      *
      * @param string|array $email
      * @param string|array $email
-     * @param string $name
-     *
-     * @return Mail
      */
      */
     public function addTo($email, string $name = null): Mail
     public function addTo($email, string $name = null): Mail
     {
     {
         if (! \is_array($email)) {
         if (! \is_array($email)) {
-            $email = \preg_split('%[,\n\r]%', (string) $email, -1, PREG_SPLIT_NO_EMPTY);
+            $email = \preg_split('%[,\n\r]%', (string) $email, -1, \PREG_SPLIT_NO_EMPTY);
         }
         }
+
         foreach($email as $cur) {
         foreach($email as $cur) {
             $cur = $this->valid(\trim((string) $cur), false, true);
             $cur = $this->valid(\trim((string) $cur), false, true);
+
             if (false !== $cur) {
             if (false !== $cur) {
-                $this->to[] = $this->formatAddress($cur, $name);
+                $this->to[$cur] = $name ?? '';
             }
             }
         }
         }
 
 
@@ -219,29 +222,23 @@ class Mail
      * Задает заголовок To
      * Задает заголовок To
      *
      *
      * @param string|array $email
      * @param string|array $email
-     * @param string $name
-     *
-     * @return Mail
      */
      */
     public function setTo($email, string $name = null): Mail
     public function setTo($email, string $name = null): Mail
     {
     {
         $this->to = [];
         $this->to = [];
 
 
-        return $this->addTo($email, $name);
+        return $this->addTo($email, $name ?? '');
     }
     }
 
 
     /**
     /**
      * Задает заголовок From
      * Задает заголовок From
-     *
-     * @param string $email
-     * @param string $name
-     *
-     * @return Mail
      */
      */
     public function setFrom(string $email, string $name = null): Mail
     public function setFrom(string $email, string $name = null): Mail
     {
     {
         $email = $this->valid($email, false, true);
         $email = $this->valid($email, false, true);
+
         if (false !== $email) {
         if (false !== $email) {
+            $this->from            = $email;
             $this->headers['From'] = $this->formatAddress($email, $name);
             $this->headers['From'] = $this->formatAddress($email, $name);
         }
         }
 
 
@@ -250,15 +247,11 @@ class Mail
 
 
     /**
     /**
      * Задает заголовок Reply-To
      * Задает заголовок Reply-To
-     *
-     * @param string $email
-     * @param string $name
-     *
-     * @return Mail
      */
      */
     public function setReplyTo(string $email, string $name = null): Mail
     public function setReplyTo(string $email, string $name = null): Mail
     {
     {
         $email = $this->valid($email, false, true);
         $email = $this->valid($email, false, true);
+
         if (false !== $email) {
         if (false !== $email) {
             $this->headers['Reply-To'] = $this->formatAddress($email, $name);
             $this->headers['Reply-To'] = $this->formatAddress($email, $name);
         }
         }
@@ -268,17 +261,12 @@ class Mail
 
 
     /**
     /**
      * Форматирование адреса
      * Форматирование адреса
-     *
-     * @param string|array $email
-     * @param string $name
-     *
-     * @return string
      */
      */
-    protected function formatAddress($email, string $name = null): string
+    protected function formatAddress(string $email, ?string $name): string
     {
     {
         if (
         if (
-            ! \is_string($name)
-            || 0 == \strlen(\trim($name))
+            null === $name
+            || ! isset($name[0])
         ) {
         ) {
             return $email;
             return $email;
         } else {
         } else {
@@ -290,10 +278,6 @@ class Mail
 
 
     /**
     /**
      * Кодирование заголовка/имени
      * Кодирование заголовка/имени
-     *
-     * @param string $str
-     *
-     * @return string
      */
      */
     protected function encodeText(string $str): string
     protected function encodeText(string $str): string
     {
     {
@@ -306,10 +290,6 @@ class Mail
 
 
     /**
     /**
      * Фильтрация имени
      * Фильтрация имени
-     *
-     * @param string $name
-     *
-     * @return string
      */
      */
     protected function filterName(string $name): string
     protected function filterName(string $name): string
     {
     {
@@ -318,10 +298,6 @@ class Mail
 
 
     /**
     /**
      * Установка папки для поиска шаблонов писем
      * Установка папки для поиска шаблонов писем
-     *
-     * @param string $folder
-     *
-     * @return Mail
      */
      */
     public function setFolder(string $folder): Mail
     public function setFolder(string $folder): Mail
     {
     {
@@ -332,10 +308,6 @@ class Mail
 
 
     /**
     /**
      * Установка языка для поиска шаблонов писем
      * Установка языка для поиска шаблонов писем
-     *
-     * @param string $language
-     *
-     * @return Mail
      */
      */
     public function setLanguage(string $language): Mail
     public function setLanguage(string $language): Mail
     {
     {
@@ -346,28 +318,27 @@ class Mail
 
 
     /**
     /**
      * Задает сообщение по шаблону
      * Задает сообщение по шаблону
-     *
-     * @param string $tpl
-     * @param array $data
-     *
-     * @throws MailException
-     *
-     * @return Mail
      */
      */
     public function setTpl(string $tpl, array $data): Mail
     public function setTpl(string $tpl, array $data): Mail
     {
     {
         $file = \rtrim($this->folder, '\\/') . '/' . $this->language . '/mail/' . $tpl;
         $file = \rtrim($this->folder, '\\/') . '/' . $this->language . '/mail/' . $tpl;
+
         if (! \is_file($file)) {
         if (! \is_file($file)) {
-            throw new MailException('The template isn\'t found (' . $file . ').');
+            throw new MailException("The template isn't found ({$file}).");
         }
         }
+
         $tpl = \trim(\file_get_contents($file));
         $tpl = \trim(\file_get_contents($file));
+
         foreach ($data as $key => $val) {
         foreach ($data as $key => $val) {
             $tpl = \str_replace('<' . $key . '>', (string) $val, $tpl);
             $tpl = \str_replace('<' . $key . '>', (string) $val, $tpl);
         }
         }
+
         list($subject, $tpl) = \explode("\n", $tpl, 2);
         list($subject, $tpl) = \explode("\n", $tpl, 2);
+
         if (! isset($tpl)) {
         if (! isset($tpl)) {
-            throw new MailException('The template is empty (' . $file . ').');
+            throw new MailException("The template is empty ({$file}).");
         }
         }
+
         $this->setSubject(\substr($subject, 8));
         $this->setSubject(\substr($subject, 8));
 
 
         return $this->setMessage($tpl);
         return $this->setMessage($tpl);
@@ -375,16 +346,14 @@ class Mail
 
 
     /**
     /**
      * Задает сообщение
      * Задает сообщение
-     *
-     * @param string $message
-     *
-     * @return Mail
      */
      */
     public function setMessage(string $message): Mail
     public function setMessage(string $message): Mail
     {
     {
         $this->message = \str_replace("\0", $this->EOL,
         $this->message = \str_replace("\0", $this->EOL,
-                         \str_replace(["\r\n", "\n", "\r"], "\0",
-                         \str_replace("\0", '', \trim($message))));
+                            \str_replace(["\r\n", "\n", "\r"], "\0",
+                                \str_replace("\0", '', \trim($message))
+                            )
+                        );
 //        $this->message = wordwrap ($this->message, 75, $this->EOL, false);
 //        $this->message = wordwrap ($this->message, 75, $this->EOL, false);
 
 
         return $this;
         return $this;
@@ -392,10 +361,6 @@ class Mail
 
 
     /**
     /**
      * Отправляет письмо
      * Отправляет письмо
-     *
-     * @throws MailException
-     *
-     * @return bool
      */
      */
     public function send(): bool
     public function send(): bool
     {
     {
@@ -434,21 +399,46 @@ class Mail
      */
      */
     protected function mail(): bool
     protected function mail(): bool
     {
     {
-        $to      = \implode(', ', $this->to);
         $subject = $this->headers['Subject'];
         $subject = $this->headers['Subject'];
         $headers = $this->headers;
         $headers = $this->headers;
         unset($headers['Subject']);
         unset($headers['Subject']);
-        $headers = $this->strHeaders($headers);
 
 
-        return @\mail($to, $subject, $this->message, $headers);
+        if (
+            1 === \count($this->to)
+            || $this->maxRecipients <= 1
+        ) {
+            foreach ($this->to as $to => $name) {
+                $headers['To'] = $this->formatAddress($to, $name);
+                $result        = @\mail($to, $subject, $this->message, $headers);
+
+                if (true !== $result) {
+                    return false;
+                }
+            }
+        } else {
+            $to        = $this->from;
+            $arrArrBcc = \array_chunk($this->to, $this->maxRecipients, true);
+
+            foreach ($arrArrBcc as $arrBcc) {
+                foreach ($arrBcc as $email => &$name) {
+                    $name = $this->formatAddress($email, $name);
+                }
+                unset($name);
+
+                $headers['Bcc'] = \implode(', ', $arrBcc);
+                $result         = @\mail($to, $subject, $this->message, $headers);
+
+                if (true !== $result) {
+                    return false;
+                }
+            }
+        }
+
+        return true;
     }
     }
 
 
     /**
     /**
      * Переводит заголовки из массива в строку
      * Переводит заголовки из массива в строку
-     *
-     * @param array $headers
-     *
-     * @return string
      */
      */
     protected function strHeaders(array $headers): string
     protected function strHeaders(array $headers): string
     {
     {
@@ -462,34 +452,66 @@ class Mail
 
 
     /**
     /**
      * Отправка письма через smtp
      * Отправка письма через smtp
-     *
-     * @throws SmtpException
-     *
-     * @return bool
      */
      */
     protected function smtp(): bool
     protected function smtp(): bool
     {
     {
         // подлючение
         // подлючение
         if (! \is_resource($this->connect)) {
         if (! \is_resource($this->connect)) {
             if (false === ($connect = @\fsockopen($this->smtp['host'], $this->smtp['port'], $errno, $errstr, 5))) {
             if (false === ($connect = @\fsockopen($this->smtp['host'], $this->smtp['port'], $errno, $errstr, 5))) {
-                throw new SmtpException('Couldn\'t connect to smtp host "' . $this->smtp['host'] . ':' . $this->smtp['port'] . '" (' . $errno . ') (' . $errstr . ').');
+                throw new SmtpException("Couldn't connect to smtp host \"{$this->smtp['host']}:{$this->smtp['port']}\" ({$errno}) ({$errstr}).");
             }
             }
             \stream_set_timeout($connect, 5);
             \stream_set_timeout($connect, 5);
             $this->connect = $connect;
             $this->connect = $connect;
             $this->smtpData(null, '220');
             $this->smtpData(null, '220');
         }
         }
 
 
-        $message = $this->EOL . \str_replace("\n.", "\n..", $this->EOL . $this->message) . $this->EOL . '.';
+        $message = $this->EOL
+            . \str_replace("\n.", "\n..", $this->EOL . $this->message)
+            . $this->EOL
+            . '.'
+            . $this->EOL;
         $headers = $this->strHeaders($this->headers);
         $headers = $this->strHeaders($this->headers);
 
 
-        // цикл по получателям
-        foreach ($this->to as $to) {
+        if (
+            1 === \count($this->to)
+            || $this->maxRecipients <= 1
+        ) {
+            $this->smtpHello();
+
+            foreach ($this->to as $email => $name) {
+                $this->smtpData("MAIL FROM:<{$this->from}>", '250');
+                $this->smtpData("RCPT TO:<{$email}>", ['250', '251']);
+                $this->smtpData('DATA', '354');
+                $this->smtpData(
+                    'To: '
+                    . $this->formatAddress($email, $name)
+                    . $this->EOL
+                    . $headers
+                    . $message,
+                    '250'
+                );
+                $this->smtpData('NOOP', '250');
+            }
+        } else {
+            $arrRecipients = \array_chunk($this->to, $this->maxRecipients, true);
+
             $this->smtpHello();
             $this->smtpHello();
-            $this->smtpData('MAIL FROM: <' . $this->getEmailFrom($this->headers['From']). '>', '250');
-            $this->smtpData('RCPT TO: <' . $this->getEmailFrom($to) . '>', ['250', '251']);
-            $this->smtpData('DATA', '354');
-            $this->smtpData('To: ' . $to . $this->EOL . $headers . $message, '250');
-            $this->smtpData('NOOP', '250');
+
+            foreach ($arrRecipients as $recipients) {
+                $this->smtpData("MAIL FROM:<{$this->from}>", '250');
+
+                foreach ($recipients as $email => $name) {
+                    $this->smtpData("RCPT TO:<{$email}>", ['250', '251']);
+                }
+
+                $this->smtpData('DATA', '354');
+                $this->smtpData(
+                    $headers
+                    . $message,
+                    '250'
+                );
+                $this->smtpData('NOOP', '250');
+            }
         }
         }
 
 
         return true;
         return true;
@@ -503,6 +525,7 @@ class Mail
         switch ($this->auth) {
         switch ($this->auth) {
             case 1:
             case 1:
                 $this->smtpData('EHLO ' . $this->hostname(), '250');
                 $this->smtpData('EHLO ' . $this->hostname(), '250');
+
                 return;
                 return;
             case 0:
             case 0:
                 if (
                 if (
@@ -510,11 +533,13 @@ class Mail
                     && '' != $this->smtp['pass']
                     && '' != $this->smtp['pass']
                 ) {
                 ) {
                    $code = $this->smtpData('EHLO ' . $this->hostname(), ['250', '500', '501', '502', '550']);
                    $code = $this->smtpData('EHLO ' . $this->hostname(), ['250', '500', '501', '502', '550']);
+
                    if ('250' === $code) {
                    if ('250' === $code) {
                        $this->smtpData('AUTH LOGIN', '334');
                        $this->smtpData('AUTH LOGIN', '334');
                        $this->smtpData(\base64_encode($this->smtp['user']), '334');
                        $this->smtpData(\base64_encode($this->smtp['user']), '334');
                        $this->smtpData(\base64_encode($this->smtp['pass']), '235');
                        $this->smtpData(\base64_encode($this->smtp['pass']), '235');
                        $this->auth = 1;
                        $this->auth = 1;
+
                        return;
                        return;
                    }
                    }
                 }
                 }
@@ -528,27 +553,23 @@ class Mail
      * Отправляет данные на сервер
      * Отправляет данные на сервер
      * Проверяет ответ
      * Проверяет ответ
      * Возвращает код ответа
      * Возвращает код ответа
-     *
-     * @param string $data
-     * @param mixed $code
-     *
-     * @throws SmtpException
-     *
-     * @return string
      */
      */
-    protected function smtpData(string $data, $code): string
+    protected function smtpData(?string $data, $code): string
     {
     {
-        if (\is_resource($this->connect)) {
+        if (\is_resource($this->connect) && null !== $data) {
             if (false === @\fwrite($this->connect, $data . $this->EOL)) {
             if (false === @\fwrite($this->connect, $data . $this->EOL)) {
                 throw new SmtpException('Couldn\'t send data to mail server.');
                 throw new SmtpException('Couldn\'t send data to mail server.');
             }
             }
         }
         }
+
         $response = '';
         $response = '';
         while (\is_resource($this->connect) && ! \feof($this->connect)) {
         while (\is_resource($this->connect) && ! \feof($this->connect)) {
             if (false === ($get = @\fgets($this->connect, 512))) {
             if (false === ($get = @\fgets($this->connect, 512))) {
                 throw new SmtpException('Couldn\'t get mail server response codes.');
                 throw new SmtpException('Couldn\'t get mail server response codes.');
             }
             }
+
             $response .= $get;
             $response .= $get;
+
             if (
             if (
                 isset($get[3])
                 isset($get[3])
                 && ' ' === $get[3]
                 && ' ' === $get[3]
@@ -557,40 +578,19 @@ class Mail
                 break;
                 break;
             }
             }
         }
         }
+
         if (
         if (
             null !== $code
             null !== $code
             && ! \in_array($return, (array) $code)
             && ! \in_array($return, (array) $code)
         ) {
         ) {
-            throw new SmtpException('Unable to send email. Response of mail server: "' . $get . '"');
+            throw new SmtpException("Unable to send email. Response of mail server: \"{$response}\"");
         }
         }
 
 
         return $return;
         return $return;
     }
     }
 
 
-    /**
-     * Выделяет email из заголовка
-     *
-     * @param string $str
-     *
-     * @return string
-     */
-    protected function getEmailFrom(string $str): string
-    {
-        $match = \explode('" <', $str);
-        if (
-            2 == \count($match)
-            && '>' == \substr($match[1], -1)
-        ) {
-            return \rtrim($match[1], '>');
-        } else {
-            return $str;
-        }
-    }
-
     /**
     /**
      * Возвращает имя сервера или его ip
      * Возвращает имя сервера или его ip
-     *
-     * @return string
      */
      */
     protected function hostname(): string
     protected function hostname(): string
     {
     {
@@ -600,17 +600,17 @@ class Mail
     }
     }
 
 
     /**
     /**
-     * Деструктор
+     * Завершает сеанс smtp
      */
      */
     public function __destruct()
     public function __destruct()
     {
     {
-        // завершение сеанса smtp
         if (\is_resource($this->connect)) {
         if (\is_resource($this->connect)) {
             try {
             try {
                 $this->smtpData('QUIT', null);
                 $this->smtpData('QUIT', null);
             } catch (MailException $e) {
             } catch (MailException $e) {
                 //????
                 //????
             }
             }
+
             @\fclose($this->connect);
             @\fclose($this->connect);
         }
         }
     }
     }