Рассылка
Заканчиваем посасывать колу на солнышке, дело не эклер, долго ждать не будет. Версия 005 нашей CMS может похвастаться наличием рассылки.
Что из себя представляет функция почтовой рассылки в Tractor Engine:
- возможность подписаться на рассылку не регистрируясь в основной таблице пользователей, достаточно просто ввести E-Mail и шлепнуть по Enter-у;
- система извещений при регистрации;
- возможность редактирования шаблонов рассылки и извещений в панели администратора;
- отсутствие вменяемой системы проверки корректности введенного адреса).
Для хранения адресов подписчиков используем отдельную таблицу. Это подчеркивает некоторую автономность и периодичность. Новостной движок подразумевает информирование о новостях далеко не всех пользователей, а только тех, которые предпочтут следить за обновлениями по почте. К слову, rss-каналы – это наш следующий шаг.
Адреса попадают в эту отдельную таблицу (te_sub) при регистрации и при подписке без регистрации посредствам специальной формы в меню. Таблица состоит всего из двух полей: уникального id и, собственно, e-mail.
Для спамофобов присутствует возможность отписаться от рассылки. Для этого нужно перейти по ссылке вида http://сайт/index.php?page=unsub&code=id_подписчика.
Как видите, защита от злодеев, которые захотят отписать вас без вашего ведома, какая-никакая присутствует. Чтобы отписаться, нужно будет указать не только e-mail, но и уникальный id, который будет вставлен в ссылку в рассылке. Узнать этот id, как вы понимаете, можно только порывшись в БД, т.е. в роли злодея может выступить разве что админ) При желании можно использовать любой уникальный код посложнее, но лично мне этого более чем достаточно.
Едем дальше. В панели администратора появился пункт «Рассылка», в котором можно:
- отправить рассылку, введя текст посредствам CKEditor (шаблон text/html);
- редактировать шаблоны рассылки и уведомлений (о подписке/отписке);
- посмотреть на счетчик подписчиков).
Соответственно в папке admin появился компонент subscribe и небольшая библиотека mail_lib.php. Сделать так, чтобы все пользователи получили письмо в нужной кодировке довольно сложно, эту задачу решают несколько функций в упомянутой библиотеке, написаны они Д. Котеровым. Еще я включил туда свою функцию для отправки извещений.
Прежде чем приступать к описанию кода, пожалуй, стоит сказать, что я не имею ни малейшего понятия насколько ресурсоемкий этот процесс отправления, скажем, 1000 писем. На локальном хосте все работает быстро и без проблем, а что скажет хостер реальный? Но в учебных целях такой опыт лишним точно не будет, прошу оставлять свое мнение по этому почтовому вопросу)
Поехали.
В двух словах, рассылка производится так. Админ заходит в панель управления, вводит текст рассылки, нажимает кнопочку «отправить», все, письма отправлены подписчикам.
В трех словах − немного сложнее. Получив текст рассылки, скрипт берет из базы данных список адресов подписчиков, оттуда же извлекает шаблон письма, в который вставляет адрес получателя и текст рассылки, отправляет письмо, так с каждым подписчиком.
В четырех словах, необходимо учесть всевозможные мелочи, такие как правильная кодировка письма, возможность зарегистрироваться в рассылке, отписаться от нее, получить уведомление о том и другом, отредактировать шаблоны, сохранить выпуски в БД, создать необходимые таблицы и записи с шаблонами при инсталляции.
Я сомневаюсь, что стоит сдесь рассматривать компонент subscription, отвечающий за регистрацию в рассылке. Он прост как пробка, проверяет корректность введенного e-mail и делает запись в БД.
Компонент rigist (напомню, компоненты у нас лежат в папке inc) теперь при регистрации проверяет наличие указанного e-mail в таблицах te_users и te_sub. Если адрес уже есть в списке рассылки (te_sub), то регистрация происходит без каких-либо предупреждений, просто делается запись в таблице пользователей (te_users).
Рассмотрим компонент unsub
<?php //Если введены код и e-mail if (isset($_REQUEST['email']) && (isset($_REQUEST['code']))) { //Проверяем соответствие между id, который знает только подписчик, и e-mail в БД. $sub_rdb = mysql_query('SELECT * FROM te_sub where email = "'.$_REQUEST['email'].'"') or die(mysql_error()); $sub_data=@mysql_fetch_array($sub_rdb); //Если введенный id не соответствует тому, что лежит в БД if ($sub_data['id'] !== $_REQUEST['code']) { $c_mes = '<tt>Не обнаружено соответствие между кодом и почтовым адресом.</tt>'; } else { //Если все гут, удаляем запись из БД $sql_del = mysql_query("DELETE FROM te_sub WHERE email='".$_REQUEST['email']."'") or die(mysql_error()); $c_mes = '<tt>"'.$_REQUEST['email'].'" удален из списка рассылки.</tt>'; //отправляем извещение по почте //подключаем почтовую библиотеку include (TE_DIR."/admin/mail_lib.php"); //вызываем оттуда функцию отправки письма $text = "Данный адрес был успешно удален из списка рассылки нашего сайта."; //получатель $to = $_REQUEST['email']; //файл шаблона $template = TE_DIR."/admin/submail.eml"; //код, по которому можно будет отписаться - уникальный id подписчика $code = $data['id']; //отправляем onemail($to, $text, $template, $code); } } $content = '<h2>Отказ от рассылки</h2><hr/> <table width=85% border=0 cellspacing=15> <tr> <td style="vertical-align: top;"> <form method="post" action="index.php?page=unsub" name="unsub"> <p><tt>Код: <br> <input class="comment" name="code" value='.@$_REQUEST['code'].'><br> Удаляемый E-Mail:</tt> <br> <input class="comment" name="email"><br><br> <input class="unit_button" name="unsub" value="Отписаться" type="submit"></p> </form></p> </td> <td style="vertical-align: top;"> <tt><p>E-Mail подписка codgust.com это:</p> <ul><li>ежемесячная лента новостей проекта Tractor Engine;</li> <li>компактно, без рекламы и графики;</li> <li>если, конечно, мне было что сказать за этот месяц)</li> </ul></tt> </td></tr></table> <span class="mes">'.$c_mes.'</span>'; ?>
Когда пользователь переходит по ссылке из письма, index.php подключает компонент inc/unsub/view.php. Последний сравнивает код и e-mail, полученные от пользователя, с теми, что лежат в таблице рассылки te_sub. Если все верно, удаляется запись с соответствующим id и отправляется извещение об успешном завершении процесса.
Собственно, функция onemail, с помощью котрой мы отправляем письмо:
function onemail($tos, $text, $eml_tmpl, $code) { // Считываем шаблон. $tmpl_rdb = mysql_query('SELECT * FROM te_support where suptitle = "notice_tmpl" ORDER BY id') or die(mysql_error()); $tmpl_data=@mysql_fetch_array($tmpl_rdb); $tpl = $tmpl_data['suptext']; // Заменяем элементы шаблона. $mail = $tpl; $mail = strtr($mail, array( "{TO}" => trim($tos), "{TEXT}" => $text, )); $mail = mailenc($mail); mailx($mail); }
Что из себя представляет функция mailx, можете посмотреть в mail_lib.php)
И компонент админ-панели subscribe.php
<?php #### РАССЫЛКА ########################################## #### админ-функции объявлены в admin/admin_lib.php #### #### а также функции общего назначения - lib.php #### #### подключаются почтовые функции mail_lib.php #### if (!isset($_SESSION['admin_login'])) include ("index.php");//Если нет авторизации. else { $date = date("d.m.Y H:i"); //счетчик подписчиков $sql_count_result = mysql_query("SELECT * FROM te_sub"); $sub_count = mysql_num_rows($sql_count_result); //функции обработки письма if (isset($_REQUEST['actmf'])) { //рассылка if (isset($_REQUEST['text'])) { if (@$_REQUEST['text'] == "") $mes = "Введите текст письиа"; elseif($sub_count == 0) $mes = "Некому рассылать"; else { include_once "mail_lib.php"; $text = $_REQUEST['text']; // Получатели письма. $result = mysql_query(" SELECT * FROM te_sub ORDER BY id ") or die(mysql_error()); for ($tos=array(); $row2=mysql_fetch_assoc($result); $tos[]=$row2); // Считываем шаблон. $tmpl_rdb = mysql_query('SELECT * FROM te_support where suptitle = "sub_tmpl" ORDER BY id') or die(mysql_error()); $tmpl_data=@mysql_fetch_array($tmpl_rdb); $tpl = $tmpl_data['suptext']; // Отправляем письма в цикле по получателям. foreach ($tos as $to) { // Заменяем элементы шаблона. $mail = $tpl; $mail = strtr($mail, array( "{TO}" => trim($to['email']), "{TEXT}" => $text, "{CODE}" => $to['id'], )); $mail = mailenc($mail); mailx($mail); } $mes = "письма разосланы "; //Сохраняем выпуск в БД //$date = date("d.m.Y H:i"); $new_tmpl=mysql_query("INSERT into te_subarchive (text, date) values ( '".$text."', '".date("d.m.Y H:i")."' );"); } } // редактор шаблона рассылки if ($_REQUEST['actmf'] == "re_sub_tmpl") { $sql_upd = mysql_query("UPDATE te_support SET suptext='".$_REQUEST['new_sub_tmpl']."' WHERE suptitle='sub_tmpl'") or die(mysql_error()); $mes = "Шаблон перезаписан"; } // редактор шаблона уведомлений if ($_REQUEST['actmf'] == "re_notice_tmpl") { $sql_upd = mysql_query("UPDATE te_support SET suptext='".$_REQUEST['new_notice_tmpl']."' WHERE suptitle='notice_tmpl'") or die(mysql_error()); $mes = "Шаблон перезаписан"; } } print ' <h2>Рассылка</h2>'; if (isset($_REQUEST['actm'])) { switch ($_REQUEST['actm']) { //Шаблон рассылки case "sub_tmpl": $rdb = mysql_query('SELECT * FROM te_support where suptitle = "sub_tmpl" ORDER BY id') or die(mysql_error()); $data=@mysql_fetch_array($rdb); $sub_tmpl=$data['suptext']; print ' Шаблон рассылки<br> <FORM ACTION="index.php?action=subscribe&actm=sub_tmpl" METHOD=POST NAME="relog"> <INPUT TYPE="hidden" NAME="actmf" VALUE="re_sub_tmpl"> <TEXTAREA NAME="new_sub_tmpl" WRAP="virtual" COLS="70" ROWS="13">'.$sub_tmpl.'</TEXTAREA><br> <INPUT TYPE="submit" VALUE="Сохранить"><br> </form><br>'; break; //Шаблон уведомлений case "notice_tmpl": $rdb = mysql_query('SELECT * FROM te_support where suptitle = "notice_tmpl" ORDER BY id') or die(mysql_error()); $data=@mysql_fetch_array($rdb); $notice_tmpl=$data['suptext']; print ' Шаблон уведомлений<br> <FORM ACTION="index.php?action=subscribe&actm=notice_tmpl" METHOD=POST NAME="relog"> <INPUT TYPE="hidden" NAME="actmf" VALUE="re_notice_tmpl"> <TEXTAREA NAME="new_notice_tmpl" WRAP="virtual" COLS="70" ROWS="13">'.$notice_tmpl.'</TEXTAREA><br> <INPUT TYPE="submit" VALUE="Сохранить"><br> </form><br>'; break; } } //Новое сооб. для рассылки if (@$_REQUEST['act'] == "mail") { ?> Новое сообщение<br> <FORM ACTION="index.php?action=subscribe&act=mail" METHOD=POST NAME="sub"> <table width="800"><tr><td> <textarea name="text"><?=$db_content?></textarea> <script type="text/javascript"> CKEDITOR.replace( 'text' ); </script> </td></tr></table> <INPUT TYPE="hidden" NAME="actmf" VALUE="send"> <INPUT TYPE="submit" VALUE="Отправить"><br> </FORM> <?php } if ($_REQUEST['act'] !== "mail") { echo '<a href="index.php?action=subscribe&act=mail">Новое сообщение для рассылки</a>'; } echo ' <table width=75% border=0 cellspacing=15><tr><td style="vertical-align: top;"> <a href="index.php?action=subscribe&actm=sub_tmpl">Шаблон рассылки</a><br> <a href="index.php?action=subscribe&actm=notice_tmpl">Шаблон уведомлений</a><br> </td><td style="vertical-align: top;"> Подписчиков: '.$sub_count.'</a><br> </td></tr></table>'; //сообщения об операциях echo "<i class=m>"; echo @$mes."</i>"; } ?>
Для наглядности, таблица te_support, содержащая шаблоны для рассылки, выглядит так.
Текст шаблона берем из поля suptext, заменяем в нем необходимые элементы и отправляем полученное письмо. Собственно шаблон рассылки:
To: {TO} Subject: Code Gust FROM: admin@codegust.com Reply-to: admin@codegust.com Content-type: text/html; charset=utf-8 {TEXT} <p>Чтобы отписаться, перейдите по ссылке <a href="http://te/index.php?page=unsub&code={CODE}">http://codegust.com/te2/index.php?page=unsub&code={CODE}</a><br> С любовью, почтовый робот.</p>
Шаблон извещений:
To: {TO} Subject: Уведомление codegust.com From: admin@codegust.com Reply-to: admin@codegust.com Content-type: text/plain; charset=utf-8 {TEXT}
И еще, после отправления рассылки, сохраняем выпуск в таблице te_subarchive. Не вижу смысла делать какие-то инструменты для просмотра старых выпусков, мы же трактор строим, если что забыл – phpMyAdmin.


[...] Словом, сегодня могу предложить только почитать про то, как написать свою CMS, и конкретно рассылку для нне. [...]
Молодец)) а то давно ничего нового не появлялось))
Если не секрет какие планы на дальнейшей развитие? и как скоро?
Сейчас даю возможность зарегистрированным пользователям добавлять записи, которые затем будут модерироваться админом) Еще прикручу функцию для автоматического обновления rss-каналов при добавлении записи. Несколько дней еще уйдет я думаю))