Автоматическое построение форм различной сложности и отправка их письмом с аттачами.
Все сталкивались с тривиальной задачей создание формы для отправки по e-mail.
Обычно не возникает никаких проблемм. Но и работа эта не столь интересна и увлекательна. Простая рутина.
Возникает идея создать программу, которая автоматизировала бы этот процесс.
Для начала определим задачу.
Предположим, нам нужно создать формы на сайте.
В формах может присутствовать:
- заголовок раздела формы
- текстовое поле (text)
- текстовый блок (textarea)
- поле пароля (password)
- поле выбора из списка (select)
- поле checkbox
- поле радио буттона (radio)
- невидимое поле (hidden)
- поле загрузки файла (file)
Отсылать письма предполагается в текстовом виде с аттачами. Письма в формате html не пользуются популярностью у народа.
Должна быть проверка на заполненность полей, обязательных к заполнению.
Вот все пункты задачи готовы.
Решено сделать 3 файла:
- файл с формой
- файл отправки формы
- файл инициализации формы
Забегая вперед, могу предположить, что кто-то захочет положить файлы программы (первые два) в отдельный каталог, например forms, и будет просто инклюдить файл с формой на нужных страницах сайта, передавая ему параметром путь к файлу инициализации данной формы. Так как формы могут все-таки отличаться друг от друга в оформлении, я не стану городить огромный файл с бесконечным количеством вариантов и приведу полностью рабочий пример, работающий на сайте «Седьмого континента» в разделе «поставщики», а также на сайте gipragor.ru в разделе «задать вопрос».
Добавлю, что в наших случаях в формах были вариации вида полей text, textarea (в форме «Седьмого континента» 3 вида поля text). В вашем случае, возможно, понядобится еще несколько вариантов для полей. Все делается аналогично тому, что будет рассмотрено ниже.
Начнем с описания файла инициализации формы.
Ниже приведен текст файла ini.php
Выбор адресата^head^0
Выберите из списка^select^1^mail консультант|info@gipragor.ru|selected админ|totoeval@mtu-net.ru
Ваши координаты^head^0
Имя^text^1
Телефон^text^0
Факс^text^0
<nobr>Е-mail</nobr>^text^0
Я хочу получить ответ по телефону^checkbox^0^checked
Вопрос^head^0
Тема^textarea^1
Вопрос^textarea^1^long
Присоединить файл^file^0^attach
Присоединить файл 2^file^0^attach2
Предыдущая страница^hidden^0^refer
Как видим, каждое поле формы описывается отдельной строкой.
Как я ни старался сделать универсальным оформление всех полей форм, не получилось.
Вследствие этого, предлагаю такое оформление:
Первым везде идет название поля, которое выводится на экран.
Вторым тип поля формы:
- text
- password
- textarea
- checkbox
- radio
- hidden
- file
Третий указатель обязательного заполнения поля. Если стоит 1 поле обязательно. Если параметр пустой или любой отличающийся от 1, то поле не обязательное.
Четвертым указываем дополнительный параметр, если он необходим. У каждого вида поля свои дополнительные параметры:
- text long указывает на то, что поле-строка будет длинной и размещена под названием; обычное поле, без параметра, размещается справа от названия
- textarea то же самое, что и у text
- checkbox checked указывает на то, что чекбокс будет выбран по-умолчанию
- radio четвертым параметром указывается имя группы радио-буттонов, а пятым checked, как и у checkbox
- file указываем имя указателя массива загружаемых файлов
- hidden указываем параметр, в соответствии с которым в значение этого поля будет подставлено определенное значение, либо параметр будет передан как есть
Ну вот покончили с инициализацией формы.
Теперь попробуем написать программу, выводящую форму пользователю.
Создаем файл index.php с нижеприведенным содержимым.
<!-- начало -->
<h1>Задать вопрос</h1>
<?
if ($is_send == "send_query")
{
echo "<p>Вопрос был отправлен.</p>";
}
?>
<!--
Выводим форму типа multipart/form-data
для отправки через нее текстовых полей и файлов
-->
<form method="post" action="send.php" ENCTYPE="multipart/form-data"
onsubmit="return Validate(this);">
<table border="0" cellspacing="0" cellpadding="5" width="100%">
<tr><td class="text" colspan="2" align="center"><b></b></td></tr>
<?
// читаем файл инициализации в массив $texts
$texts=file("ini.php");
// перебираем все строки в файле и определяем пустые
for ($j=0; $j<(sizeof($texts)); $j++)
{
// оператором trim удаляем у строки слева и справа пробелы и переносы
$texts[$j]=trim($texts[$j]);
// если есть пустые строки, то в новый массив $proposal_text они не записываются
if ($texts[$j] != "") {$proposal_text[]=$texts[$j];}
}
// обнуляем переменную, в которую будут занесены все обязательные для заполнения поля
$fields=""; // имена полей формы
$fieldnames=""; // названия полей формы
// перебираем все строки инициализации в массиве $proposal_text
// имена полей будут называться form[0], form[1], form[2]...
// Таким образом, мы передадим всю форму в одном массиве.
// Индекс элемента массива будет указателем строки описания поля в файле инициализации
// для дальнейшей обработки полученной формы.
for ($i=0; $i<(sizeof($proposal_text)); $i++)
{
// разобьем строки специальным разделительным символом ^
// тогда $proposal[0] - текстовое название поля
// тогда $proposal[1] - указатель типа поля формы:
// text - текстовое поле-строка
// textarea - текстовое поле-блок
// hidden - невидимое поле
// password - поле ввода пароля
// file - форма для загрузки файла
// checkbox - чекбокс
// radio - радио буттон
// head - заголовки разделов форм, не имеют никаких полей,
// лишь текст выводится полужирным шрифтом, либо выделяется иным способом
// тогда $proposal[2] - указатель обязательного заполнения поля посетителем.
//Если он равен 1, то поле обязательно, если любое другое значение - нет
// тогда $proposal[3] - дополнительный параметр.
// например, у нас это:
// long в поле text и поле textarea означает, что поле бОльшей ширины
// и расположено под названием поля
// refer в поле hidden говорит о том, что передается в невидимом поле
// адрес предыдущей страницы, посещенной пользователем
// attach в поле file - имя поля загружаемого пользователем файла
// все поля оформляются соответственно указанному типу ниже в блоке switch
$proposal=explode('^',$proposal_text[$i]);
// переменной type присвоем тип поля
$type=trim($proposal[1]);
// определяем, обязательно ли к заполнению текущее поле
if (isset($proposal[2]))
{
if (trim($proposal[2]) == '1')
// если в поле указателя содержится 1, то добавляем имя поля к
{
// если в переменную fields уже были записаны данные,
// то ставим запятую
if ($fields != "") {$fields.=', ';}
$fields.="'form[$i]'";
if ($fieldnames != "") {$fieldnames.=', ';}
$fieldnames.="'".$proposal[0]."'";
$imperative=" *";
}
else {$imperative="";}
}
// если в строке есть дополнительный параметр, то записываем его в пtременную param
if (isset($proposal[3])) {$param=trim($proposal[3]);}
// стравниваем тип поля с возможными вариантами и соответственно оформляем его
switch ($type) {
case "head": // поле заголовка
echo "<tr>\n\t".
"<td class=\"text\" colspan=\"2\"><br>".
"<p><b>$proposal[0]</b></p>".
"</td>\n</tr>\n";
break;
case "text": // текстовое поле
if (isset($proposal[3])) {
if ($param == "long") {
// если поле длинное, то располагаем его под названием
// и увеличиваем длину
echo "<tr>\n\t".
"<td colspan=\"2\" class=\"text\">".
$proposal[0]."$imperative<div align=\"right\">\n\t".
"<input type=\"text\" name=\"form[$i]\" size=\"102\">".
"</div></td>\n</tr>\n";
}
}
else {
// иначе выводим стандартное поле-строку справа от названия поля
echo "<tr>\n\t".
"<td class=\"text\">".$proposal[0]."$imperative</td>\n\t".
"<td align=\"right\" valign=\"top\">".
"<input type=\"text\" name=\"form[$i]\" size=\"50\">".
"</td>\n</tr>\n";
}
break;
case "password": // поле пароля
echo "<tr>\n\t".
"<td class=\"text\">".$proposal[0]."$imperative</td>\n\t".
"<td align=\"right\" valign=\"top\">".
"<input type=\"password\" name=\"form[$i]\" size=\"50\">".
"</td>\n</tr>\n";
break;
case "textarea": // поле текстового блока оформляем аналогично текстовому полю
if (isset($proposal[3])) {
if ($param == "long") {
echo "<tr>\n\t".
"<td colspan=\"2\" class=\"text\">".
$proposal[0]."$imperative".
"<div align=\"right\">\n\t".
"<textarea name=\"form[$i]\" rows=\"6\" cols=\"102\">".
"</textarea></div></td>\n</tr>\n";
}
}
else {
echo "<tr>\n\t".
"<td class=\"text\" valign=\"top\">".
$proposal[0]."$imperative</td>\n\t".
"<td align=\"right\" valign=\"top\">".
"<textarea name=\"form[$i]\" rows=\"4\" cols=\"50\">".
"</textarea></td>\n</tr>\n";
}
break;
case "radio": // радио буттон.
//Его дополнительный параметр - имя переменной-группы радио-буттонов.
if (!isset($proposal[3])) {$param = "form[$i]";}
if (!isset($proposal[4])) {$checked = "";}
// если не задан параметр выбора буттона по-умолчанию
else {$checked = " checked";}
// если выбран по-умолчанию
echo "<tr>\n\t".
"<td colspan=\"2\" class=\"text\">".
"<input type=\"radio\" name=\"$param\" id=\"id$i\"$checked>".
"<label for=\"id$i\"> $proposal[0]</label></td>\n</tr>\n";
break;
case "checkbox": // чекбокс
if (!isset($proposal[3])) {$checked = "";}
// если не задан параметр выбора чекбокса по-умолчанию
else {$checked = " checked";}
// если выбран по-умолчанию
echo "<tr>\n\t".
"<td colspan=\"2\" class=\"text\">".
"<input type=\"checkbox\" name=\"form[$i]\" id=\"id$i\"$checked>".
"<label for=\"id$i\"> $proposal[0]</label></td>\n</tr>\n";
break;
case "hidden": // невидимое поле.
// От его параметра зависит, что в нем будет передаваться.
// Если параметр не описан, то он будет передан по-умолчанию как есть
if (!isset($proposal[3])) {$param = "form[$i]";}
echo "<input type=\"hidden\" name=\"form[$i]\"";
if ($param=="refer") {echo " value=\"".urlencode($HTTP_REFERER)."\">";}
else {echo " value=\"$param\">\n";}
break;
case "file": // поле загружаемого пользователем файла
if (!isset($proposal[3])) {$param = "form[$i]";}
echo "<tr>\n\t".
"<td align=\"right\" valign=\"bottom\">".
"<p align=\"left\">$proposal[0]$imperative<br>".
"<input type=\"file\" name=\"file_att[$param]\" size=\"35\"></p>".
"</td></tr>\n";
break;
case "select": // поле выбора селект
if (isset($proposal[3])) { // если заданы параметры селекта
$options = explode("\t", $proposal[3]);
// разделяем параметры каждой строки селекта
$option_text=explode("|",$option[0]);
// разбиваем первый подпараметр селекта
// на имя селекта и вид (multiselect и обычный)
// получаем в $option_text[1] - вид селекта
if ($option_text[1]=="multiselect")
{
if (isset($option_text[2]))
{
$multiselect="size=$option_text[2]";
}
$multiselect.=" multiselect";
}
else {$multiselect=" size=\"1\"";}
echo "<tr>\n\t".
"<td class=\"text\">$proposal[0]$imperative</td>\n\t".
"<td align=\"right\" valign=\"top\">".
"<select name=\"form[$i]\" style=\"width: 317\"$multiselect>\n";
// выводим тег селекта
for ($z=1; $z<sizeof($options); $z++)
// в 0 строке селекта у нас параметр, указывающий отправщику,
// как обрабатывать текущий селект
{
// выводим строки селекта
$option_text=explode("|", $options[$z]);
// в первой части - текст строки,
// во второй - передаваемое значение
if (!isset($option_text[2])) {$option_text[2]="";}
// если параметр "выбранная строка" не установлен
echo "\t<option value=\"$option_text[1]\" $option_text[2]>".
"$option_text[0]</option>\n";
// вывели строку селекта
}
echo "</select></td>\n</tr>\n";
}
break;
default:
// если тип не определен, то ничего не выводится.
// И, следовательно, стоит подумать, что еще не учтено.
}
}
?>
</table>
<!--
Выведена таблица с формой.
Осталось вывести на экран кнопки "отправить" и "очистить",
как это делают умные дядьки на других сайтах.
-->
<table border="0" cellspacing="5" cellpadding="0" width="100%">
<tr>
<td align="right" valign="bottom"><input type="submit" value="Отправить">
<img src="/images/1x1.gif" width="10" height="50">
<input type="reset" value="Очистить">
</td>
</tr>
</table>
<!--
Конечно, здесь могло не быть этого кода,
а кнопки отправки формы и очищения можно задать в файле инициализации,
добавив и их обработку в программе.
-->
</form>
<p>Вы можете задать вопрос.
С вопросом можно отправить файлы.<br>
Ответ вы получите на адрес электронной почты,
указанный в координатах,
либо по телефону, если поставите галочку у соответствующего пункта.</p>
<!--
Яваскрипт, которому мы передали список полей формы, обязательных к заполнению
Он определит после попытки отправки формы, заполненны ли эти поля.
Если не заполнены, то скрипт ругнется и укажет какое поле не заполненно,
установив в него курсор.
-->
<script language="JavaScript">
fields = new Array(<? echo $fields; ?>);
fieldnames = new Array(<? echo $fieldnames; ?>);
function Validate(forma)
{
for(i=0;i<fields.length;i++)
{
field = fields[i];
if (forma.elements[field].value == "")
{
alert("Вы должны заполнить поле \""+fieldnames[i]+"\"");
forma.elements[field].focus();
return false;
}
}
return true;
}
</script>
</td>
</tr>
</table>
<!-- конец -->
Ну вот, наша форма выводится на экран пользователя, и он старательно, прикусив язык, заполняет все её поля.
Но мы-то знаем, что вывести форму и заполнить её половина дела. Важно получить форму, обработать её и отправить по выбранному или указанному по-умолчанию адресу.
Ниже приведен текст файла отправки письма с аттачами send.php, который мы кладем в папку с index.php.
<?
// определяем, с какой страницы пришел посетитель на страницу отправки
if (strpos($HTTP_REFERER, "gipragor.ru/feedback") === false) {
// если не со страницы отправки формы, то кидаем его в форму
header("location: .");
}
// если посетитель прошел проверку, читаем файл инициализации
$texts=file("ini.php");
// перебираем все строки в файле и сохраняем в новый массив только не пустые
for ($j=0; $j<(sizeof($texts)); $j++)
{
$texts[$j]=trim($texts[$j]);
if ($texts[$j] != "") {$proposal_text[]=$texts[$j];}
}
// Объявляем пустую строковую переменную, в которой будет храниться сообщение
$mailtext="";
// Перебираем все строки массива формы
for ($i=0; $i<(sizeof($proposal_text)); $i++)
{
// Разбиваем строки по разделительному символу ^
// получаем подстроки, в которых хранится:
// 0 - текст названия поля формы
// 1 - тип поля формы
// 2 - указатель обязательности заполнения поля формы
// 3 - дополнительные параметры поля формы
$proposal=explode('^',$proposal_text[$i]);
$type=trim($proposal[1]);
if (isset($proposal[3])) {$proposal[3]=trim($proposal[3]);}
if (!isset($form[$i])) {$form[$i]="нет данных";}
// перебираем варианты типов полей формы
switch ($type)
{
case "head": // если заголовок раздела формы
if ($mailtext != "")
{
// если это не первый заголовок в форме,
// то ставим перед ним 2 пустые строки
$mailtext.="\n\n";
}
$mailtext.="\t$proposal[0]\n";
break;
case "text": // если поле текствое - строка
if (isset($proposal[3]))
{
if ($proposal[3] == "long")
{ // если строка длинная, то выводим ее под названием поля
$mailtext.="$proposal[0]:\n$form[$i]\n\n";
}
}
else
{ // если строка не длинная, то выводим ее справа от названия поля
$mailtext.="$proposal[0]: $form[$i]\n";
}
break;
case "textarea": // поле текстового блока
$mailtext.="$proposal[0]:\n$form[$i]\n\n";
break;
case "radio": // радио буттон
$group == "$proposal[2]";
$mailtext.="$proposal[0]: $group\n";
break;
case "checkbox": // чекбокс
if (trim($form[$i]) == "on")
{ // если чекбокс выделили, то его значение - on
$mailtext.="$proposal[0]\n";
}
break;
case "hidden": // скрытое поле. Обрабатываем его взависимости от параметра
if (!isset($proposal[3])) {$param = "form[$i]";}
if ($param="refer") {$form[$i]=urldecode($form[$i]);}
$mailtext.="$proposal[0]: $form[$i]\n";
break;
case "file": // поле файла отправляемого пользователем в виде аттача к письму
if (!isset($proposal[3])) {$param = "form[$i]";}
else {$param=$proposal[3];}
// создаем массив из файлов-аттачей
$att_arr[]=$file_att[$param];
$att_arr_type[]=$file_att_type[$param];
$att_arr_name[]=$file_att_name[$param];
break;
case "select": // поле селекта
if (isset($proposal[3])) { // если есть параметры селекта
$options = explode("\t", $proposal[3]);
// разбиваем параметры и перебираем каждую строку
$option_text=explode("|",$option[0]);
// разбиваем первый подпараметр селекта
//на имя селекта и вид (multiselect и обычный)
if ($option_text[0] == "mail") {$mailto=$form[$i];}
// если 0 параметр равен mail -
// значит это варианты e-mail адресата (в нашем случае)
$mailtext.="$proposal[0]: $form[$i]\n";
}
break;
default:
}
}
// удалим из текста формы теги html
$mailtext=strip_tags($mailtext);
// удалим специальные символы из текста формы
// используя стандартный способ из руководства php
$search = array ("'&(amp|#38);'i",
"'&(lt|#60);'i",
"'&(gt|#62);'i",
"'&(nbsp|#160);'i",
"'&(iexcl|#161);'i",
"'&(cent|#162);'i",
"'&(pound|#163);'i",
"'&(copy|#169);'i",
"'(\d+);'e",
"''i",
"''i");
$replace = array ("&",
"<",
">",
" ",
chr(161),
chr(162),
chr(163),
chr(169),
"chr(\\1)",
" - ",
"-");
$mailtext = preg_replace ($search, $replace, $mailtext);
// Параметры отправляемого сообщения
if ($mailto == "")
{ // если адресат не был выбран в форме, то указываем его по-умолчанию
$to = "totoeval@mtu-net.ru";
}
else
{ // если адресат был выбран посетителем в форме
$to = $mailto;
}
$from = "webmaster@$SERVER_NAME";
$subject = "New Providers";
$message = $mailtext;
// объявление в заголовке письма параметр From - от кого.
$headers = "From: $from";
// Оформляем boundary string - строку-разделитель
$semi_rand = md5(time());
$mime_boundary = "==Multipart_Boundary_x{$semi_rand}x";
// определяем, был ли отправлен файл с письмом
if (sizeof($att_arr)>0)
{ // если файл отправлен
// Добавляем к заголовку письма тип передаваемых данных
$headers .= "\nMIME-Version: 1.0\n" .
"Content-Type: multipart/mixed;\n" .
" boundary=\"{$mime_boundary}\"";
// Добавляем к сообщению multipart boundary и тип передаваемых данных,
// а затем присоединяем текст письма
$message = "This is a multi-part message in MIME format.\n\n" .
"--{$mime_boundary}\n" .
"Content-Type: text/plain; charset=\"windows-1251\"\n" .
"Content-Transfer-Encoding: 7bit\n\n" .
$message . "\n\n";
}
else
{ // если письмо без приаттаченных файлов
// Добавляем к заголовку письма тип передаваемых данных
$headers .= "\nMIME-Version: 1.0\n" .
"Content-Type: text/plain; charset=\"windows-1251\"\n" .
" boundary=\"{$mime_boundary}\"";
// Добавляем к сообщению boundary и тип передаваемых данных (текст),
// а затем присоединяем текст письма
$message = "Content-Type: text/plain; charset=\"windows-1251\"\n" .
"Content-Transfer-Encoding: 7bit\n\n" .
$message . "\n\n";
}
// перебираем имеющиеся приаттаченные файлы
// если их нет, то аттач производиться не будет
for ($files=0; $files<sizeof($att_arr); $files++)
{
$fileatt=$att_arr[$files];
$fileatt_type=$att_arr_type[$files];
$fileatt_name=$att_arr_name[$files];
if (is_uploaded_file($fileatt))
{ // проверяем, верно ли заапплоаден файл
// Читаем файл аттача ('rb' = читаем в двоичном виде)
$file = fopen($fileatt,'rb'); // открываем поток
$data = fread($file,filesize($fileatt));
fclose($file); // закрываем поток
// Кодируем Base64 содержимое файла
$data = chunk_split(base64_encode($data));
// Добавляем содержимое файла к сообщению
// с соответствующими заголовком и описанием типа данных
$message .= "--{$mime_boundary}\n".
"Content-Type: {$fileatt_type};\n".
" name=\"{$fileatt_name}\"\n".
"Content-Transfer-Encoding: base64\n\n".
$data."\n\n";
}// так перебираем все отправляемые файлы
}
$message .= "--{$mime_boundary}--\n";
// в конец сообщения добавляем разделительную строку с окончанием сообщения
// Отправляем сообщение
@mail($to, $subject, $message, $headers);
// сообщаем в куки, что письмо отправлено
setcookie ("is_send", "send_query", time()+120);
// переводим пользователя к странице формы
// где ему сообщат, что письмо его отправлено
header("location: .");
?>
Результатом работы программы будет письмо, приходящее на выбранный или указанный по-умолчанию адрес. С письмом может быть прислано произвольное, установленное в инициализации количество файлов-аттачей.
Все вышенаписанное предназначено для начинающих программировать на php, и для полета мысли уже опытных.
Повторять программу можно в варианте моем, своем, друга.
По вопросам всвязи с возникающими проблемами обращаться ко мне по адресу totoeval@mtu-net.ru
Тотоев Александр
totoeval@mtu-net.ru
www.nova-wings.ru
gipragor.ru
www.v6.ru
«Седьмой континент»
|