Notice: Constant articledir already defined in /var/www/students/public_html/pages/pm01/zrr/constants.php on line 2

Notice: Constant articlelist already defined in /var/www/students/public_html/pages/pm01/zrr/constants.php on line 3

Notice: Constant bkcolor already defined in /var/www/students/public_html/pages/pm01/zrr/constants.php on line 5

Notice: Constant frontcolor already defined in /var/www/students/public_html/pages/pm01/zrr/constants.php on line 6

Notice: Constant newsfile already defined in /var/www/students/public_html/pages/pm01/zrr/constants.php on line 7
zRrr - Гостевая Книга
Новости Статьи Гостевая Ссылки О Сайте

Гостевая книга

Гостевая книга - это такая страничка на вашем сайте, где пользователи смогут оставлять свои сообщения, общаться, высказывать предложения и пожелания (самое, пожалуй, неприятное :) ). Для тех же целей служат и форумы, где одновременно может обсуждаться несколько тем, но написание форума - дело не самое простое. Итак - гостевая книга.

Между показами, сообщения, оставленные посетителями, нужно где-то хранить. Хотя в нашем случае, скорее "как-то", поскольку хранить их мы точно будем в файле. Варианта у меня два. Во-первых, можно обрабатывать полученное сообщение и сразу же дописывать его в файл в оформленом виде. Во-вторых, можно просто сохранять в файл сами данные, и оформлять их когда пользователь запросит страницу гостевой. У каждого метода есть свои плюсы и минусы, но об этом позже.

Как это все будет выглядеть? Пользователь вписывает в поля формы свое имя (ник), тему сообщения, само сообщение и адрес электронной почты (по желанию). После нажатия кнопки "Добавить", данные передаются скрипту по методу POST. Скрипт так или иначе сохраняет данные и отсылает пользователю новую страничку. Начнем:

Гостевая книга Вариант-1
<?php

define('FileName', 'gbdata.txt');                         //зададим константы: файл с записями 
define('bkcolor', '#b0e0e6');                             //цвет фона 
define('frontcolor', '#87ceeb');                          //еще один цвет 

function SaveMsg($author, $email, $topic, $msg)           //эта функция сохраняет сообщене 
{
  $oldfile = file(FileName);                              //читаем все записи в массив 

  if (get_magic_quotes_gpc()):                            //если magic_quotes_gpc включено 
    $author = stripslashes($author);                      //заменяем все escape
    $email  = stripslashes($email);                       //последовательности
    $topic  = stripslashes($topic);                       //(удаляем слэши)
    $msg    = stripslashes($msg);
  endif;

  $search  = array('<', '>');                             //заменяем в полученых данных знаки
  $replace = array('&lt;', '&gt;');                       //'<' и '>' на '&lt;', '&gt;'

  $author = str_replace($search, $replace, $author);      //чтобы не было проблем с тегами
  $email  = str_replace($search, $replace, $email);       //которые кто-нибудь обязательно введет
  $topic  = str_replace($search, $replace, $topic);
  $msg    = str_replace($search, $replace, $msg);

  $fd = @fopen(FileName, 'w')                             //открываем фал для перезаписи
    or die('<B>Не удалось открыть файл сообщений для записи.</b>');

  //Если указан адрес электронной почты, оформляем имя автора как ссылку
  $a = ($email != '') ? "<A href = mailto:$email>$author</a>" : "$author";
  //иначе оставляем как есть

  //начинаем сохранять сообщение
  fwrite($fd, "<TR bgcolor='".frontcolor."'>              //оформляем как таблицу
  <TD width = '18%' align = 'center'><B>".date('d.m.Y-H:i')."</b></td>    //дата
  <TD width = '17%' align = 'center'>$a</td><TD width = '65%'>$topic</td> //автор и тема
</tr>
<TR bgcolor='".bkcolor."'>
  <TD colspan = '3'><TABLE width = '98%' align = 'center'><TR><TD>");
  //сам текст сообщения
  $res = preg_match_all("/.+/", $msg, $found);            //разбиваем сообщение на строки
  for($i = 0; $i < $res; $i++):                           //каждую строку сохраняем как отдельный абзац
    fwrite($fd, "<P>". trim($found[0][$i]) ."</P>\r\n");
  endfor;

  fwrite($fd, "  </td></tr></table></td>                  //закрываем ячейку таблицы
</tr>");
  
  fwrite($fd, implode('', $oldfile));                     //вставляем старые записи
}

function PrintForm()                                      //эта функция печатает форму и 
{ ?>                                                      //все записи гостевой книги
<html>                                                    //выходим из обработки PHP
<head>                                                    //эта часть страницы не содержит переменных
  <TITLE>Гостевая книга.</title>                          //так что ее можно выводить без обработки
</head>
<SCRIPT>                                                  //однако интеграция
<!--- Главное, чтобы этого не увидел древний браузер      //чтобы проверить, заполнены ли поля формы
function CheckEmpty(f, theName) {                         //воспользуемся функцией на java-script
  if (f.value.length == 0) {
    alert('Не заполнено поле \'' + theName + "\' ");
    f.focus();
    f.select();
    return false;
  } else {
    return true;
  }
}
-->
</SCRIPT>
<body>

<?php                                                     //а вот теперь начинаем использовать переменные
  print("<FORM action='{$_SERVER['PHP_SELF']}' method='POST'  //форма передает параметры этому же скрипту
    onSubmit = \"return (CheckEmpty(this.author, 'Автор')
      && CheckEmpty(this.topic, 'Тема') && CheckEmpty(this.msg, 'Сообщение'))\"> 
      //проверили, все ли необходимые поля заполнил пользователь      
  //вывод формы
  <TABLE border = '0' width = '760' align = 'center'>
  <TR bgcolor='".frontcolor."'>
    <TD width = '80'> Имя: </td>
    <TD> <input type='text' name='author' size='45' tabindex='1'></td>
    <TD width = '80'> e-mail: </td>
    <TD> <input type='text' name='email' size='45' tabindex='2'></td>
  </tr>
  <TR bgcolor='".bkcolor."'>
    <TD> Тема: </td>
    <TD colspan = '3' align = 'left'> <input type='text' name='topic' size='108' tabindex='3'></td>
  </tr> 
  <TR bgcolor='".frontcolor."'>
    <TD colspan = '4'> <textarea name='msg' rows='10' cols='92' tabindex='5'></textarea></td>
  </tr>
  <TR bgcolor='".bkcolor."'>
    <TD colspan = '4' align = 'left'> <input type='submit' name='submit' value='Добавить' tabindex='6'></td>
  </tr>
  </table>
  <INPUT type='hidden' name='action' value='submitted'>
</form>
");
  print("<TABLE border = '0' width = '760' align = 'center'>\r\n"); //теперь печатаем сохраненные сообщения
  print(implode('', file(FileName)));   //внутри файла уже все оформлено, можно спокойно его выводить
  print("</table>\r\n");
  print("</body>
</html>");
}

//все функции оформлены, основная часть

//если есть параметр 'action' и он равен 'submitted', значит пользователь передал данные
if (isset($_POST['action']) && $_POST['action'] == 'submitted'):
  SaveMsg($_POST['author'], $_POST['email'], $_POST['topic'], $_POST['msg']); //сохраняем сообщение в файл
  //посылаем браузеру заголовок перехода, чтобы он вновь загрузил страницу с книгой
  header("Location: http://".$_SERVER['HTTP_HOST'].$_SERVER['PHP_SELF']);
  //на тот случай, если браузер нас не понял.
  print("<p>Сообщение успешно добавлено</p>");
  print('<a href="http://' . $_SERVER['HTTP_HOST'].$_SERVER['PHP_SELF'].'>Возврат</a>');
else:
  PrintForm();    //а если пользователь не передал данные, просто выводим форму.
endif;

?>

Что нового:

  • define(строка имя, значение [, логическое регистронезависимая]) - создает константу с указаным именем и значением. Если дополнительный параметр не задан, считается регистрозависимой. Имена констант и переменных определены в разных пространствах имен и могут совпадать.
  • file(строка имя_файла [, целое использовать_include_path]) - возвращает содержимое файла в виде массива. Элемент массива - строка из файла с символами конца строки. Если возникает ошибка, возвращается FALSE. Если использовать_include_path = '1', то указаный файл ищется так же в каталогах, заданых настройкой include_path.
  • get_magic_quotes_gpc() - показывает, включена ли опция magic_quotes_gpc в настройках PHP. Если она включена, во всех данных, получаемых скриптом спецсимволы (одинарная и двойная кавычки и т.п.) будут предваряться обратным слешем ('\'). Это нужно для записи в базы данных.
  • stripslashes(строка стр) - удаляет '\' перед спецсимволами. Возвращает результирующую строку.
  • str_replace(найти_что, заменить_на, искать_где) - заменяет части строки или массива. Аргументами служат строки и массивы. Если найти_что и заменить_на - массивы одинаковой длины, замена осуществляется на соответствующий элемент. Подробнее см. Руководство.
  • preg_match_all(строка шаблон, строка стр, массив результат) - функция, работающая с регулярными выражениями в стиле Perl. В массиве результат содержатся все найденые совпадения. В нашем случае, это последовательность, содержащая один или более ('+') любых символов, кроме символа конца строки ('.'). То есть строка. Поскольку в разных операционных системах конец строки определяется разными символами ('\n' - Unix, '\r\n' - Windows), и в файл сохраняются строки, завершающиеся '\r\n', после получения строки вызывается trim()
  • implode(строка связка, массив части) - возвращает строку состоящую из элементов массива части, разделенных строкой связка.
  • $_SERVER['PHP_SELF'] - содержит относительный адрес скрипта относительно сервера. Т.е. для www.site.ru/zrrr/test.php результат будет zrrr/test.php
  • $_POST - суперглобальный массив, содержащий все параметры, переданые скрипту методом POST. Ключем служит имя параметра.
  • $_SERVER['HTTP_HOST'] - содержит адрес сервера, взятый из заголовка запроса.

Итак, что происходит, когда пользователь переходит на страницу с нашей книгой? Скрипт проверяет, установлен ли параметр 'action' и равен ли он 'submitted'. Естественно нет, так как пользователь зашел первый раз, и скрипт выводит форму. Далее, пользователь заполняет поля формы и жмет 'Ok'. Функция на java-script проверяет, заполнил ли он нужные поля, если нет, то выводит сообщение и передача данных не происходит. Передача данных производится методом POST и все данные из заполненых полей формы будут доступны через суперглобальный массив $_POST. Обработкой данных занимается этот же скрипт (<FORM action='{$_SERVER['PHP_SELF']}' method='POST'), но теперь обработчик пойдет по другой ветке и сохранит сообщение в файл. Дальше мы, с помощью заголовка перехода header("Location: http://".$_SERVER['HTTP_HOST'].$_SERVER['PHP_SELF']); просим браузер клиента загрузить наш скрипт еще раз. Зачем? Дело в том, что если гостевая будет выведена сразу же просле обработки полученых данных, а пользователь захочет обновить страницу, его браузер снова пошлет все данные. Скрипт снова их обработает и у нас будет 2 одинаковых сообщения, вместо одного. Или больше. Это называется флуд (от англ. flood - наводнение). Можно сделать это иначе, с помощью тега HTML <META>. Для этого пользователю нужно выдать страницу с сообщением об успешной отправке и внутри блока <HEAD></head> вставить <meta http-equiv="refresh" content="задержка" url="http://'.$_SERVER['HTTP_HOST'].$_SERVER['PHP_SELF'].'"> Задержка - время в секундах, после которого браузер запросит указанную страницу. Недостаток - надо красифо оформлять страницу с сообщением :)

Кроме того, при сохранении мы заменяем все символы '<' и '>' на их коды: '&lt;' и '&gt;' соответственно. Это сделано для того, чтобы теги HTML, введенные пользователем отображались, а не рассматривались браузером как элементы кода со всеми вытекающими последствиями.

Этот скрипт быстрее загружает данные (не надо их оформлять) за счет чуть более медленного сохранения. Однако у него есть один недостаток: однажды сохраненные данные сложно изменить. Только если передавать весь файл. Далее будет рассмотрен скрипт, сохраняющий данные в чистом виде.

Гостевая книга Вариант-2

//Приведены функции сохранения и загрузки сообщений, а так же часть PrintForm()

function SaveMsg($author, $email, $topic, $msg)
{
  $oldfile = file(FileName);                           //загружаем старые сообщения в массив
  if( $oldfile != FALSE && count($oldfile) > 0):       //если файл считался без ошибок и массив не пуст
    $reccount = $oldfile[0] + 1;                       //номер новой записи - номер старой +1
  else:                                                //(номер всегда в первой строке)
    $reccount = 1;                                     //иначе это первая запись
  endif;

  if (get_magic_quotes_gpc()):
    $author = stripslashes($author);                   //все как всегда
    $email  = stripslashes($email);
    $topic  = stripslashes($topic);
    $msg    = stripslashes($msg);
  endif;

  $search  = array('<', '>');
  $replace = array('&lt;', '&gt;');

  $author = str_replace($search, $replace, $author);
  $email  = str_replace($search, $replace, $email);
  $topic  = str_replace($search, $replace, $topic);
  $msg    = str_replace($search, $replace, $msg);

  $fd = @fopen(FileName, 'w')
    or die('<B>Не удалось открыть файл сообщений для записи.</b>');

  fwrite($fd, "$reccount\r\n");                        //сохраняем номер сообщения
  fwrite($fd, "$author\r\n");                          //автора
  fwrite($fd, "$email\r\n");                           //адрес почты
  fwrite($fd, "$topic\r\n");                           //тему
  fwrite($fd, date('d.m.Y-H:i')."\r\n");               //дату сообщения
  fwrite($fd, "$msg\r\n");                             //сообщение
  fwrite($fd, ">>>\r\n");                              //метку конца сообщения
   
  fwrite($fd, implode('', $oldfile));                  //дописываем остальной файл
  fclose($fd);
}

function GetMessage($fd)                               //Функция извлекает из файла сообщение
{
  $recnum = fgets($fd);                                //считываем номер записи
  $author = fgets($fd);                                //автора
  $mail   = fgets($fd);                                //"мыло"
  $topic  = fgets($fd);                                //тему
  $date   = fgets($fd);                                //дату

  //если хоть одно поле не считалось, загрузка неудачна, выходим, возвращаем FALSE
  if ($recnum == FALSE || $author == FALSE || $mail == FALSE || 
    $topic == FALSE || $date == FALSE): return(FALSE); endif;
  $tmp = fgets($fd);                                   //читаем строку
  while ($tmp != ">>>\r\n" && $tmp != FALSE):          //пока читается и прочитали не метку
    $msg[] = trim($tmp);                               //обрезаем символы конца строки и вносим в массив
    $tmp = fgets($fd);                                 //считываем новую строку и повторяем процесс
  endwhile;
  $res = array('recnum' => trim($recnum),              //собираем все в массив
               'author' => trim($author),
               'mail'   => trim($mail),
               'topic'  => trim($topic),
               'date'   => trim($date),
               'msg'    => $msg
              );
  return($res);                                        //и возвращаем
}

//часть функции PrintForm(). Нужно еще выводить саму форму и последние теги.

  print("<TABLE border = '0' width = '760' align = 'center'>\r\n");

  $fd = @fopen(FileName, "r")                          //открываем файл для чтения
    or die('<B>Не удалось открыть файл сообщений.</b>');
  $item = GetMessage($fd);                             //получаем первое сообщение
  while ($item != FALSE):                              //пока сообщения "получаются"
    $a = ($item['mail'] != "") ? "<A href = 'mailto:" . $item['mail'] . "'>" . 
      $item['author'] . "</a>" : $item['author'];

    print("<TR bgcolor='".frontcolor."'>               //красиво их выводим
  <TD width = '18%' align = 'center'><B>{$item['date']}</b></td>
  <TD width = '17%' align = 'center'>{$a}</td><TD width = '65%'>{$item['topic']}</td>
</tr>

<TR bgcolor='".bkcolor."'>
  <TD colspan = '3'><TABLE width = '98%' align = 'center'><TR><TD>\r\n");
  foreach ($item['msg'] as $m): 
    print("    <P>". $m ."</p>\r\n");
  endforeach;

  print("  </td></tr></table></td>
</tr>");

    $item = GetMessage($fd);                            //берем следующее сообщение 
  endwhile;

Вот так. Теперь по номеру можно однозначно определять сообщение, и, к примеру, загружать его в форму для редактирования. Или просто удалить. Кроме того, можно сделать постраничный вывод, поскольку файл явно разбивается на сообщения. Правда возрасли расходы на оформление текста. Кстати, есть идея использовать в качестве меток комментарии HTML. Таким образом можно и сохранять уже оформленые сообщения и неплохо различать их. Попробуете сделать? :)


Вологодский Государственный Педагогический Университет

сделал Р. 'zRrr' Золотов

Рейтинг@Mail.ru