Автоматическое построение форм различной сложности и отправка их письмом с аттачами произвольного количества
Все сталкивались с тривиальной задачей - создание формы для отправки по e-mail. Обычно не возникает никаких проблемм. Но и работа эта не столь интересна и увлекательна. Простая рутина. Возникает идея создать программу, которая автоматизировала бы этот процесс. Для начала определим задачу. Предположим, нам нужно создать формы на сайте.
В формах может присутствовать:
- заголовок раздела формы
- текстовое поле (text)
- текстовый блок (textarea)
- поле пароля (password)
- поле выбора из списка (select)
- поле checkbox
- поле радио буттона (radio)
- невидимое поле (hidden)
- поле загрузки файла (file)
Отсылать письма предполагается в текстовом виде с аттачами. Письма в формате html не пользуются популярностью у народа. Должна быть проверка на заполненность полей, обязательных к заполнению. Вот все пункты задачи готовы.
Решено сделать 3 файла:
- файл с формой
- файл отправки формы
- файл инициализации формы
Забегая вперед, могу предположить,
Добавлю, что в наших случаях в формах были вариации вида полей 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, и для полета мысли уже опытных.
Повторять программу можно в варианте моем, своем, друга.
По вопросам всвязи с возникающими проблемами обращаться ко мне по адресу totoeval@mtu-net.ru