Рейтинг@Mail.ru
 

Шаг 5

Альтернатиивные варианты

Мы уже предупреждали, что не все почтовые клиенты распознают HTML разметку. Так вот, что бы письмо, сформированное в формате HTML доходило корректно, желательно иметь две версии текста - обычный и гипертекст. Тогда почтовый клиент сам выберет тот, с которым сможет справиться. Если разметкой не злоупотреблять (таблицы, флоат и прочие изыски), то можно сделать автоматический дубляж, очистив текстовую часть письма от тегов. Для каждой из частей необходимо установить свой заголовок и расположить части письма в строгом порядке.

Тип multipart имеет три субтипа - mixed, alternative и related, которые используются синтаксически одинаково, но имеют разное предназначение

mixed - используется, когда в рамках одного почтового сообщения имеется несколько независимых друг от друга, и равнозначных частей. Самый простой пример такого письма - сообщение с вложением.

alternative - используется, когда в одном почтовом сообщении содержится несколько частей, содержащих одну и ту же информацию, предназначенную для отображения на различном клиентском ПО - например текстовая и HTML версия одного и того же письма.

related - используется, когда в одном почтовом сообщении содержится несколько частей, формирующих один итоговый документ. Яркий пример - HTML письмо с картинками. Запомните, по стандарту только в этом случае должны работать ссылки на Contend-id элементов (вида <img src="cid:image">).

Порядок частей, в котором они указаны в письме, зачастую имеет ключевое значения для того, как будет отображаться сообщение у клиента.

mixed - порядок частей для наших задач не имеет значения.

alternative - части должны быть расставлены по порядку, от более простых к более сложным. RFC регламентирует процесс выбора одной из версий письма клиентом пользователя примерно так:
"В общем случае, почтовый клиент должен отображать последнюю доступную ему версию документа".
Т.е. при формировании текстовой и HTML-версий письма необходимо вперед поставить текстовую.

related - первой в очереди должна идти основная часть (HTML документ, например). Следом - все остальные.

Что бы лучше понять, как это устроено, посмотрите на схему:


То есть у нас должно быть письмо, в котором два альтернативных варианта. А почтовый клиент выберет один - тот с которым сможет справится. Если поддерживает HTML, то последний, а нет - простой текстовый.

Разделяются части письма так называемым boundary. Это не что иное, как произвольная строка, которой не будет в потоке данных.

Сгенерируем её прямо в конструкторе нашего класса:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php

    
private $boundary
# Конструктор. Устанавливаем разделитель и текст сообщения          
   
function __construct() 
   {        
      
$this->boundary "--".md5(uniqid(time()));

      if(
$message)                   
      
$this->message $message
      else 
      
$this->errors[] = 'There is no message text';           
   }


Её нужно указать в главном заголовке. Тогда она будет воспринята в теле письма, как разделитель частей.
Так и пропишем:
1
2
3
4
5

$header 
"Content-type: multipart/alternative; boundary=\""$this->boundary ."\"\r\n"
;


Ну и сразу организуем текстовую часть письма, потому что она будет присутствовать всегда, как самая легкая версия из альтернативных:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

   
public function __construct($message false)   
   {   
      
$this->boundary '=='uniqid(time());  
              
      if(
$message)  
      {                    
          
$this->message    $message;  
          
$this->setHtml(false);                 
      }  
      else  
          
$this->errors[] = 'There is no message text';                              
   }

А часть письма с разметкой организуем в методе setHtml(), предварительно очистив первую часть от тегов и присоединив через разделитель отформатированный текст. Снабдив его предварительно подобающими заголовками.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

   
public function setHtml($set false)   
   {      
      
      
$this->headers  =  "--"$this->boundary .
"\r\n";  
      
$this->headers .= "Content-type: text/plain; charset=\"utf-8\"\r\n";   
      
$this->headers .= "Content-Transfer-Encoding: base64\r\n\r\n";  
       
      if(
$set
      {      
          
$this->multipart  $this->headers;   
          
$this->multipart .= chunk_split(base64_encode(strip_tags($this->message))) .
"\r\n";    
          
$this->multipart .= "--"$this->boundary .
"\r\n";        
          
$this->multipart .= "Content-type: text/plain; charset=\"utf-8\"\r\n";  
          
$this->multipart .= "Content-Transfer-Encoding: base64\r\n\r\n";  
          
$this->multipart .= chunk_split(base64_encode($this->message)) .
"\r\n"
      } 
      else  
      { 
          
$this->multipart  $this->headers chunk_split(base64_encode($this->message)) .
"\r\n";  
      }     
   }

И еще, от греха подальше, лучше закодировать в base64 и само сообщение. Тогда уж точно мышь не проскочит.
Теперь наш класс будет выглядеть так:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
<?php

class IRB_Mailer   
{   
    public 
$to;  
    public 
$from;      
    public 
$subject;      
    public 
$message;
    public 
$errors = array();      
    private 
$boundary;      
    private 
$headers;      
    private 
$multipart;  
    

/**  
* Constructor.     
* @param string $message        
* @Establishes a symbol of carrying over of a line and dividers 
*/          
   
public function __construct($message false)   
   {   
      
$this->boundary '=='uniqid(time());  
              
      if(
$message)                     
          
$this->message    $message;                   
      else  
          
$this->errors[] = 'There is no message text';                              
   } 
      
/**  
* Sets message type to HTML.  
* Устанавливает HTML формат сообщения  
* @access public           
* @return void  
*/    
   public function setHtml($set false)   
   {      
      
      
$this->headers  =  "--"$this->boundary .
"\r\n";  
      
$this->headers .= "Content-type: text/plain; charset=\"utf-8\"\r\n";   
      
$this->headers .= "Content-Transfer-Encoding: base64\r\n\r\n";  
       
      if(
$set
      {      
          
$this->multipart  $this->headers;   
          
$this->multipart .= chunk_split(base64_encode(strip_tags($this->message))) .
"\r\n";    
          
$this->multipart .= "--"$this->boundary .
"\r\n";        
          
$this->multipart .= "Content-type: text/plain; charset=\"utf-8\"\r\n";  
          
$this->multipart .= "Content-Transfer-Encoding: base64\r\n\r\n";  
          
$this->multipart .= chunk_split(base64_encode($this->message)) .
"\r\n"
      } 
      else  
      { 
          
$this->multipart  $this->headers chunk_split(base64_encode($this->message)) .
"\r\n";  
      }     
   }
 
      
/**  
* Adds a "To" address..  
* Устанавливает адрес "Кому"  
* @access public  
* @param string  $to           
* @return void  
*/     
   
public function createTo($to '')   
   {   
      if(empty(
$to))   
           
$this->errors[] = 'There is no addressee';    
      elseif(!
$this->checkEmail($to))  
           
$this->errors[] = 'The e-mail address is not correct';   
       else  
           
$this->to $to;  
   }  
      
/**  
* Adds a "From" address.  
* Устанавливает адрес "От кого"  
* @access public  
* @param string  $from           
* @return void  
*/     
   
public function createFrom($from false)   
   {   
      if(
$from)      
         
$this->from trim(preg_replace('/[\r\n]+/'' '$from));   
      else   
         
$this->errors[] = 'There is no sender';         
   }       
      
/**  
* Adds a Subject.  
* Устанавливает тему сообщения  
* @access public  
* @param string  $subject           
* @return void  
*/      
   
public function createSubject($subject false)   
   {   
      if(
$subject)   
          
$this->subject '=?utf-8?b?'base64_encode($subject) .'?=';   
      else   
          
$this->errors[] = 'There is no theme';         
   } 
          
/**  
* Deduces a script error.  
* Проверка корректности электронного адреса  
* @param string  $to      
* @access private     
* @return string or boolean  
*/        
   
private function checkEmail($to)   
   {   
       if (
function_exists("filter_var"))  
           return 
filter_var($toFILTER_VALIDATE_EMAIL); 
       else 
           return 
preg_match("/^[a-z0-9_\.-]+@([a-z0-9]+\.)+[a-z]{2,4}$/i"$to); 
   }     
        
/**   
* Method of formation of headings  
* Метод формирования заголовков   
* @access private    
* @param string  $subject            
* @return void   
*/             
   
private function createHeader()  
   {  
      
$header "Content-type: multipart/alternative; boundary=\""$this->boundary ."\"\r\n";            
    
   $header .= "From: "$this->from ." <"$this->from ."> \r\n";  
   
    $header .= "MIME-Version: 1.0\r\n"
   
    $header .= "Date: "date('D, d M Y h:i:s O') ."\r\n";
         return 
$header;    
   }  
        
/**  
* Deduces a script error.  
* Диагностика ошибок      
* @access private     
* @return string or boolean  
*/       
   
private function checkData()   
   {   
      if(
count($this->errors))    
          return 
implode(PHP_EOL$this->errors);   
      else   
          return 
false;     
   }        
        
/**  
* Sends mail using the PHP mail() function.  
* Отправляет письмо используя PHP функцию  mail()     
* @access public     
* @return string   
*/     
   
function sendMail()  
   {           
           
         if(!
$error $this->checkData())  
         {     
            
$header $this->createHeader();  
                     
            if(!
mail($this->to$this->subject$this->multipart$header'-f'$this->from))  
                return 
'Letter sending is impossible';  
            else  
                return 
NULL;  
        }  
        else  
        {  
            return 
$error;  
        }  
   }    


   
////////////////////////////////////////////////////////////////////////////// 
    
$to 'email@mail.ru';       
    
$subject 'Табе пакет';   
    
$from 'email@yandex.ru';       
    
$message '<h1 style="color:blue">Вот такое вот письмо</h1>';   

       
    
$mail = new IRB_Mailer($message);   
    
$mail -> setHtml(true);   
    
$mail -> createTo($to);   
    
$mail -> createFrom($from);   
    
$mail -> createSubject($subject);   
    
$error $mail -> sendMail();   
    echo  
nl2br($error);


Ну теперь то совсем все в ажуре. Осталось прилепить файл и все будут довольны.
Так в чем же дело? Сейчас или никогда!


Об этом следующий раздел.