Наши партнеры

UnixForum





Библиотека сайта rus-linux.net

InfoCity - виртуальный город компьютерной документации
www.ifcity.info

 

Автоматическое построение форм различной сложности и отправка их письмом с аттачами произвольного количества

Тотоев Александр

Все сталкивались с тривиальной задачей ≈ создание формы для отправки по 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("|",$options[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, и для полета мысли уже опытных.

Повторять программу можно в варианте моем, своем, друга.