Статьи - Автоматическое построение форм различной сложности
Автоматическое построение форм различной сложности
Автоматическое построение форм различной сложности и отправка их письмом с аттачами произвольного количества
Все сталкивались с тривиальной задачей - создание формы для отправки по 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: ."); ?>
Результатом работы программы будет письмо, приходящее на выбранный или указанный по-умолчанию адрес. С письмом может быть прислано произвольное, установленное в инициализации количество файлов-аттачей.