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

Все сталкивались с тривиальной задачей - создание формы для отправки по 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> 	". "<td class="text" colspan="2"><br>". "<p><b>$proposal[0]</b></p>". "</td> </tr> "; break; case "text": // текстовое поле if (isset($proposal[3])) { if ($param == "long") { // если поле длинное, то располагаем его под названием // и увеличиваем длину echo "<tr> 	". "<td colspan="2" class="text">". $proposal[0]."$imperative<div align="right"> 	". "<input type="text" name="form[$i]" size="102">". "</div></td> </tr> "; } } else { // иначе выводим стандартное поле-строку справа от названия поля echo "<tr> 	". "<td class="text">".$proposal[0]."$imperative</td> 	". "<td align="right" valign="top">". "<input type="text" name="form[$i]" size="50">". "</td> </tr> "; } break; case "password": // поле пароля echo "<tr> 	". "<td class="text">".$proposal[0]."$imperative</td> 	". "<td align="right" valign="top">". "<input type="password" name="form[$i]" size="50">". "</td> </tr> "; break; case "textarea": // поле текстового блока оформляем аналогично текстовому полю if (isset($proposal[3])) { if ($param == "long") { echo "<tr> 	". "<td colspan="2" class="text">". $proposal[0]."$imperative". "<div align="right"> 	". "<textarea name="form[$i]" rows="6" cols="102">". "</textarea></div></td> </tr> "; } } else { echo "<tr> 	". "<td class="text" valign="top">". $proposal[0]."$imperative</td> 	". "<td align="right" valign="top">". "<textarea name="form[$i]" rows="4" cols="50">". "</textarea></td> </tr> "; } break; case "radio": // радио буттон. //Его дополнительный параметр - имя переменной-группы радио-буттонов. if (!isset($proposal[3])) {$param = "form[$i]";} if (!isset($proposal[4])) {$checked = "";} // если не задан параметр выбора буттона по-умолчанию else {$checked = " checked";} // если выбран по-умолчанию echo "<tr> 	". "<td colspan="2" class="text">". "<input type="radio" name="$param" id="id$i"$checked>". "<label for="id$i"> $proposal[0]</label></td> </tr> "; break; case "checkbox": // чекбокс if (!isset($proposal[3])) {$checked = "";} // если не задан параметр выбора чекбокса по-умолчанию else {$checked = " checked";} // если выбран по-умолчанию echo "<tr> 	". "<td colspan="2" class="text">". "<input type="checkbox" name="form[$i]" id="id$i"$checked>". "<label for="id$i"> $proposal[0]</label></td> </tr> "; 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"> ";} break; case "file": // поле загружаемого пользователем файла if (!isset($proposal[3])) {$param = "form[$i]";} echo "<tr> 	". "<td align="right" valign="bottom">". "<p align="left">$proposal[0]$imperative<br>". "<input type="file" name="file_att[$param]" size="35"></p>". "</td></tr> "; break; case "select": // поле выбора селект if (isset($proposal[3])) { // если заданы параметры селекта $options = explode("	", $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> 	". "<td class="text">$proposal[0]$imperative</td> 	". "<td align="right" valign="top">". "<select name="form[$i]" style="width: 317"$multiselect> "; // выводим тег селекта for ($z=1; $z<sizeof($options); $z++) // в 0 строке селекта у нас параметр, указывающий отправщику, // как обрабатывать текущий селект { // выводим строки селекта $option_text=explode("|", $options[$z]); // в первой части - текст строки, // во второй - передаваемое значение if (!isset($option_text[2])) {$option_text[2]="";} // если параметр "выбранная строка" не установлен echo "	<option value="$option_text[1]" $option_text[2]>". "$option_text[0]</option> "; // вывели строку селекта } echo "</select></td> </tr> "; } 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.=" "; } $mailtext.="	$proposal[0] "; break; case "text": // если поле текствое - строка if (isset($proposal[3])) { if ($proposal[3] == "long") { // если строка длинная, то выводим ее под названием поля $mailtext.="$proposal[0]: $form[$i] "; } } else { // если строка не длинная, то выводим ее справа от названия поля $mailtext.="$proposal[0]: $form[$i] "; } break; case "textarea": // поле текстового блока $mailtext.="$proposal[0]: $form[$i] "; break; case "radio": // радио буттон $group == "$proposal[2]"; $mailtext.="$proposal[0]: $group "; break; case "checkbox": // чекбокс if (trim($form[$i]) == "on") { // если чекбокс выделили, то его значение - on $mailtext.="$proposal[0] "; } break; case "hidden": // скрытое поле. Обрабатываем его взависимости от параметра if (!isset($proposal[3])) {$param = "form[$i]";} if ($param="refer") {$form[$i]=urldecode($form[$i]);} $mailtext.="$proposal[0]: $form[$i] "; 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("	", $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] "; } 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 .= " MIME-Version: 1.0 " . "Content-Type: multipart/mixed; " . " boundary="{$mime_boundary}""; // Добавляем к сообщению multipart boundary и тип передаваемых данных, // а затем присоединяем текст письма $message = "This is a multi-part message in MIME format. " . "--{$mime_boundary} " . "Content-Type: text/plain; charset="windows-1251" " . "Content-Transfer-Encoding: 7bit " . $message . " "; } else { // если письмо без приаттаченных файлов // Добавляем к заголовку письма тип передаваемых данных $headers .= " MIME-Version: 1.0 " . "Content-Type: text/plain; charset="windows-1251" " . " boundary="{$mime_boundary}""; // Добавляем к сообщению boundary и тип передаваемых данных (текст), // а затем присоединяем текст письма $message = "Content-Type: text/plain; charset="windows-1251" " . "Content-Transfer-Encoding: 7bit " . $message . " "; } // перебираем имеющиеся приаттаченные файлы // если их нет, то аттач производиться не будет 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} ". "Content-Type: {$fileatt_type}; ". " name="{$fileatt_name}" ". "Content-Transfer-Encoding: base64 ". $data." "; }// так перебираем все отправляемые файлы } $message .= "--{$mime_boundary}-- "; // в конец сообщения добавляем разделительную строку с окончанием сообщения // Отправляем сообщение @mail($to, $subject, $message, $headers); // сообщаем в куки, что письмо отправлено setcookie ("is_send", "send_query", time()+120); // переводим пользователя к странице формы // где ему сообщат, что письмо его отправлено header("location: ."); ?> 

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

 
Автор: Максим Ковальский
 
Оригинал статьи: http://www.woweb.ru/publ/59-1-0-485