Рассылка

Заканчиваем посасывать колу на солнышке, дело не эклер, долго ждать не будет. Версия 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.

Пятница, 23 Июль 2010 Пишем CMS

Отзывов: 3 на Рассылка

  1. [...] Словом, сегодня могу предложить только почитать про то, как написать свою CMS, и конкретно рассылку для нне. [...]

  2. Оффтоп | Старицкий Т. on 23 Июль 2010
  3. Молодец)) а то давно ничего нового не появлялось))
    Если не секрет какие планы на дальнейшей развитие? и как скоро?

  4. Сергей on 30 Июль 2010
  5. Сейчас даю возможность зарегистрированным пользователям добавлять записи, которые затем будут модерироваться админом) Еще прикручу функцию для автоматического обновления rss-каналов при добавлении записи. Несколько дней еще уйдет я думаю))

  6. Noisy Wizard on 30 Июль 2010

Ваш отзыв