PHP: Полезные приемы
Автор: Антон Орлов, orlovs.pp.ru

  Главная   Учебник   Статьи   FAQ   Книги   Ссылки  

Глава 9. PHP: гостевая книга

Вы наверняка неоднократно встречали в Интернете такой сервис, как гостевые книги - web-страницы, на которых каждый посетитель может оставить свой отзыв, который будет виден другим посетителям, впоследствии зашедшим на страницу. И, скорее всего, вы думали, что создание гостевой книги требует долгого и сложного программирования.

Не спорю, - сделать крупный Интернет-сервис, предоставляющий всем желающим гостевые книги для установки на сайт, довольно трудно. Однако на языке программирования PHP организация простой системы оставления сообщений может быть создана всего несколькими строками кода. Причем нетрудно заставить эти строки обслуживать сразу несколько отдельных гостевых книг - скажем, дать посетителям возможность оставлять свои комментарии и отзывы на различные материалы, размещенные на сайте, на тех же самых страницах, что эти материалы размещены. Или просто организовать разные книги для разных категорий посетителей.

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


1. Предварительные изыскания

Схема работы сценария простой гостевой книги такова.

" Для хранения сообщений выделена специальная папка, в которой каждое сообщение хранится в отдельном текстовом файле. Такая схема, бесспорно, не является оптимальной в плане быстродействия, но для среднепосещаемого сайта вполне сгодится.

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

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

Как осуществить такую нумерацию?

Наиболее простое решение - присваивать каждому файлу с сообщением имени, включающим в себя последовательно возрастающий номер. Чтобы этот номер в первом сообщении был меньше, чем номер во втором, во втором - меньше, чем в третьем и т.д. Генерировать же такой номер проще всего с помощью интересной функции PHP time(); она выдает количество секунд между 1 января 1970 года (этот момент считается началом "эпохи Unix") и текущим временем, так называемую "временную метку Unix". (В настоящее время эта величина - чуть больше миллиарда.) Посмотрите - если имя файла с сообщением составлять из индекса гостевой книги и временной метки Unix (см. на рис.9.1), то, во-первых, каждое сообщение будет обладать своим уникальным именем (посылка нескольких сообщений в гостевую книгу разными пользователями в одну и ту же секунду теоретически возможна, но маловероятна), а, во-вторых, их легко можно будет отсортировать по времени появления (время ведь вспять не течет - каждое новое сообщение будет получать большую метку, нежели любое предыдущее).

Бесспорно, так как сортировка имен, состоящих из индекса и временной метки, будет проводиться по законам сортировки строк (то есть, скажем, 21 будет стоять раньше 3 при сортировке по возрастанию - т.к. сравнение ведется с начала строки), то при увеличении разрядности временной метки новые сообщения окажутся посреди старых. Однако какие-либо проблемы в нашем случае начнутся не раньше момента достижения временной меткой значения в 10 миллиардов, а до него еще больше, чем две с половиной сотни лет.


Рис. 9.1. Гостевая книга - все ее файлы.

2. Алгоритм

Есть, впрочем, еще одно пожелание.

Обратите внимание - если следовать данному алгоритму, то сообщение, помещенное посетителем в гостевую книгу, будет просто вставлено в текст той страницы, на которой она расположена. А это значит, что какой-нибудь злоумышленник вполне может поместить в сообщение гостевой книги код на PHP - и он будет преспокойно выполнен! Результат же такого выполнения окажется непредсказуем для владельца сайта - это может быть и удаление с сайта всех файлов, и размещение на нем совсем не того, что хотелось бы, и массовая почтовая рассылка... Поэтому наш алгоритм следует дополнить еще одним пунктом:

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

Ну, а теперь посмотрим, как все это реализуется на PHP.

3. Файлы с гостевой книгой

Чтобы облегчить собственный труд, вынесем код гостевой книги, выводящий сообщения и форму ввода, в отдельный файл - назовем его niz.php - и будем его включать в web-страницы с помощью команды include. Нам останется разве что помещать перед этой командой уникальный код для каждой книги, чтобы сообщения разных книг не перепутывались.

<?php
$ind="уникальная аббревиатура книги, без пробелов и специальных символов, например, book01";
include ("niz.php");
?>

Данный фрагмент кода следует помещать именно там, где должны располагаться на web-странице сообщения гостевой книги.

Код обработки введенного посетителем отзыва и записи его в файл поместим в файле otziv.php.

4. Вывод сообщений и формы

Содержимое файла niz.php с собственно кодом гостевых книг может быть следующим.

<?php

Укажем имя папки, в которой будут сохраняться отзывы (ее вначале надо будет создать на аккаунте сайта вручную). Само имя может быть любым - важно лишь, чтобы оно не содержало пробелов или специальных символов:

$dirct="gb";

Далее следует уже знакомый сценарий "Папкопотрошилки" (см. главу 6), применяемый к этой самой папке с отзывами. Вот практически точно такой же, как и в "Папкопотрошилке", код, записывающий в массив $a[] имена всех файлов, в имени которых содержится указанный выше индекс книги:

$hdl=opendir($dirct);
while (($file = readdir($hdl))!== false)
{
if (strstr($file, $ind)!=False)
    {
    $a[]=$file;
    }
}
closedir($hdl);

Примечание:

Функция opendir (имя папки) - открывает указанную в ее параметре папку для чтения списка находящихся в ней файлов так же, как команда fopen делает это с файлом - записывая "внутреннее имя" в дескриптор.

Функция readdir (дескриптор) - при каждом вызове возвращает имя одного из файлов (или папки), находящихся в открытой командой opendir директории, дескриптор которой указан в параметре функции. Когда список файлов исчерпывается, возвращает false.

Функция strstr (строка для поиска, искомое) - ищет в своем первом аргументе строку, указанную вторым аргументом, и возвращает True, если ее там находит.

Функция fclose (дескриптор) и closedir (дескриптор) - выгружают из памяти указанные дескрипторы.

Теперь отсортируем полученный массив. Для этого сначала узнаем количество сообщений книги:

$l=sizeof($a);

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

if ($l!=0)
{
rsort($a);

(Можно было и не использовать переменную $l, а сразу написать: if (sizeof($a)!=0)...)

Теперь массив $a содержит имена файлов с сообщениями, причем в первых элементах массива содержатся имена файлов с наибольшими номерами (т.е. самые новые - как и следует из приведенного выше алгоритма). Если же требуется обратный порядок (т.е. чтобы новые сообщения помещались в конец страницы), то вместо функции rsort (сортировка по убыванию) следует использовать функцию sort (т.е. сортировка по возрастанию).

Вставим все файлы с сообщениями в страницу с гостевой книгой с помощью оператора include, перебрав последовательно элементы массива с именами этих файлов конструкцией foreach:

foreach ($a as $value)
{
include ("$dirct/$value");
echo ("<br>(разделитель сообщений)");
}

Как уже говорилось, foreach считывает в указанную в его параметрах переменную - в данном случае $value - все элементы массива - в данном случае $a - по очереди, выполняя каждый раз указанный после него в фигурных скобках код, в котором указанная переменная может использоваться. Поскольку в массиве первыми идут элементы с именами файлов с наиболее новыми сообщениями, то и на странице эти сообщения появятся сверху.

Код вывода имеющихся сообщений завершен.

}
?>

Теперь осталось разместить на странице форму для добавления нового сообщения. В ее заголовке укажем имя файла, в котором будет размещен код добавления нового отзыва - в нашем случае otziv.php:

<form method="post" action="otziv.php">

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

<input name="nom" type="hidden" value="<?php echo ($ind); ?>">

Комментарий:

Скрытое поле (типа hidden) не отображается в браузере, однако передается вместе с формой.

Ну и - непосредственно поле ввода сообщения, уже, ясное дело, не скрытое:

<textarea name="otziv" cols="60" rows="10" wrap="virtual"></textarea>

Вездесущая кнопка отправки формы:

<input name="submit" type="submit" value="Добавить отзыв"></form>

После нажатия этой кнопки программе-обработчику будет передан массив $HTTP_POST_VARS[] с элементами $HTTP_POST_VARS['nom'] с индексом гостевой книги и $HTTP_POST_VARS['otziv'] с текстом сообщения, а также массив $_POST с аналогичным содержимым.

Примечание:

В первых версиях PHP все переменные, передаваемые через формы, были доступны в сценариях по их именам, указанным в параметрах name форм.

В PHP версий 4.0 и старше эти переменные также доступны в массиве $HTTP_POST_VARS['имя элемента'].

В PHP версий 4.1 и старше эти переменные также доступны в массиве $_POST['имя элемента'].

На доступность передаваемых переменных также влияют параметры в файле php.ini:


5. Обработчик новых отзывов

Теперь осталось сделать программу-обработчик новых отзывов - otziv.php. Как это ни удивительно, но она уместится всего в пять строк.

<?php

Укажем сценарию имя папки с отзывами:

$dirct="gb";

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

Сгенерируем имя для нового файла с сообщением - просто соединим вместе индекс гостевой книги и временную метку Unix, полученную функцией time():

$otznam=$HTTP_POST_VARS['nom'].time();

Теперь создадим новый файл со сгенерированным именем и откроем его для записи - все это делается одной командой - fopen с параметром w+.

Примечание:

Функция fopen ("имя файла", "способ открытия") - это команда открытия файла. Для того, чтобы из программы на PHP считать содержимое какого-либо файла или записать в него данные, этот файл нужно сначала открыть - командой fopen (так уж устроен PHP). При этом открытому файлу присваивается некое "внутреннее имя" (Справедливости ради стоит сказать, что такая фраза не совсем корректна по сути, но для практической работы подобный взгляд вполне можно использовать) - так называемый дескриптор, и именно его возвращает функция fopen. Первый параметр fopen - имя файла (вместе с относительным или абсолютным путем к нему), второй - способ открытия файла.

В зависимости от второго параметра функции fopen файл может быть открыт по-разному - для чтения, для записи, с очисткой содержимого или нет. Возможные параметры fopen такие:

r - открыть файл только для чтения и приготовиться читать его с начала.

r+ - открыть файл для чтения и для записи и приготовиться работать с ним с его начала.

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

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

a - открыть файл только для записи и приготовиться дописывать данные в его конец. Если файл с указанным именем не существует, то создается новый файл с таким именем.

a+ - открыть файл для записи и для чтения и приготовиться дописывать данные в его конец. Если файл с указанным именем не существует, то создается новый файл с таким именем.

Открываемый файл может располагаться и на удаленном сервере - в этом случае он будет доступен только для чтения независимо от параметров открытия файла. Путь к файлу в таком следует указывать полностью - начиная с http:// или ftp://.

(В нашем случае можно также использовать параметр a+ - различие этих двух параметров, заключающееся в том, что fopen с параметром w+ очищает все содержимое открываемого файла, а fopen с параметром a+ нет, несущественно, так как файл все равно создается новый):

$hdl = fopen("$dirct/$otznam", "w+");

Проводить операции записи или чтения из файла средствами PHP можно только через дескриптор этого файла - некое "внутреннее имя", "поток вывода данных". Именно дескриптор, а не имя файла, придется указывать в функциях, совершающих эти действия. Дескриптор создается при открытии файла функцией fopen, которая его и возвращает - в данном случае он записывается в переменную $hdl.

Теперь запишем в открытый файл отзыв (находящийся в элементе массива $HTTP_POST_VARS['otziv']), предварительно убрав из него специальной командой возможные тэги HTML и команды PHP - дабы обезопасить сайт от действий злоумышленников:

fwrite($hdl,strip_tags($HTTP_POST_VARS['otziv']));

Примечание:

Команда fwrite (дескриптор файла, записываемая в файл строка) записывает указанную во втором параметре строку в файл, дескриптор которого указан в ее первом параметре.

То место в файле, с которого совершается чтение данных и в которое осуществляется запись, называется указателем файла. (Если файл представить как тетрадь, то указатель - это открытая страница, вернее, номер открытой страницы.) При открытии файла командой fopen с параметрами r, r+, w или w+ указатель файла ставится на его начало, а при открытии с параметром a или а+ - в самый конец.

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

Функция strip_tags(строка) вырезает из строки, указанной в ее параметрах, все тэги - то есть "все в угловых скобках", как HTML, так и PHP, ASP и другие, возвращая эту строку с вырезанными тэгами. Если какие-либо тэги вырезать не следует, то их можно перечислить во втором параметре данной функции: команда

strip_tags(строка, '<a><b><i><u>');

вырежет из указанной в первом параметре строки все тэги, кроме <a>, <b>, <i>, <u>, то есть оставит посетителю возможность оформлять ими свой текст.

В результате злоумышленник не сможет разместить в гостевой книге ни HTML-текст, ни PHP-программу, а значит, не сможет ни испортить дизайн сайта, ни выполнить на нем какие-либо свои команды PHP.

Вместо полного удаления всех тэгов из отзыва можно провести конвертацию содержащихся в нем специальных символов - угловых скобок, кавычек, амперсандов - в их эквиваленты, просто отображающие эти символы на экране. Это делает команда htmlspecialchars:

fwrite($hdl,htmlspecialchars($HTTP_POST_VARS['otziv']));

Примечание:

Функция htmlspecialchars(строка) конвертирует все "специальные символы" в указанной в ее параметре строке в так называемые "мнемоники HTML", которые отображаются браузером на странице как эти самые символы. Конвертация происходит следующим образом:

Сочетания символов "&amp;", "&quot;", "&lt;", "&gt;" отображаются в браузере как амперсанд, двойная кавычка, знаки "меньше" и "больше" соответственно.

В PHP третьей (начиная с подверсии 3.0.17) и четвертой (начиная с подверсии 4.0.3) версий в качестве второго аргумента можно также указать параметр ENT_QUOTES или ENT_NOQUOTES. Если указан первый, то помимо вышеуказанных замен выполняется еще и замена символа ' (одинарной кавычки) на сочетание символов &#039;, а если указан второй - то никакие кавычки не заменяются.

В результате все тэги будут отображаться на экране точно так же, как при их вводе в поле ввода сообщения - то есть не станут обрабатываться браузером или интерпретатором PHP.

Комментарий:

При написании программ на PHP обратите внимание, что любую информацию, запрашиваемую от посетителя и впоследствии выводимую на web-страницу, весьма желательно перед выводом обработать функцией htmlspecialchars или strip_tags - для обеспечения устойчивости сайта к взлому.

Ибо в том случае, если вы на одной странице запрашиваете у посетителя e-mail, а на другой - выводите его на web-страницу, злоумышленник в поле ввода e-mail'а может поместить код на PHP, и тогда, будучи включенным в выведенную страницу без какой-либо обработки, этот код благополучно исполнится! А ведь в этом коде может быть что угодно - вплоть до команд удаления файлов. Поэтому не забывайте обрабатывать данными функциями все информацию, что была введена в элементы формы и будет отображаться на какой-либо странице.

Даже скрытые поля и выпадающие списки могут нести в себе угрозу безопасности сайта - если получаемая из них информация выводится на экран. Ничто не мешает злоумышленнику сделать локальную копию страницы с формой на своем жестком диске, прописать в качестве страницы-обработчика полный путь к ней - вместе с адресом сайта и изменить в форме содержимое любых полей, в том числе и скрытых, поместив туда PHP-код. Поэтому же не надейтесь на Javascript-сценарии проверки "валидности формы" - удалить их из локальной копии web-страницы нетрудно.

Так что будьте бдительны!

Если Вы желаете, чтобы при отображении на странице сообщений сохранялась их разбивка отправителями на абзацы, то обработайте записываемое в файл сообщение командой nl2br для конвертации символов конца строки в тэги <br>, которые этот разрыв строки и означают:

fwrite($hdl,nl2br(strip_tags($HTTP_POST_VARS['otziv'])));

или, если хотите, запишите все команды обработки записываемого сообщения в две строки:

$otziv=nl2br(strip_tags($HTTP_POST_VARS['otziv']));
fwrite($hdl,$otziv);

Примечание:

Функция nl2br(строка) вставляет перед каждым символом начала строки, встреченном в строке в ее параметре, тэг <br> - в PHP версии до 4.0.5, или <br /> - в PHP более поздних версий. (Последний тэг совместим и с языком XML.)

Файл можно закрыть - и закончить сценарий.

fclose($hdl);
?>

Рис.2. Всего три файла - и гостевая книга. А можно даже два.

6. Объединение файлов

При работе вышеприведенного сценария после добавления нового сообщения в гостевую книгу посетитель окажется на странице обработки отзывов - в данном случае otziv.php. Можно поместить на ней, например, благодарность ему за добавленное сообщение.

Однако куда как лучше будет, если после добавления нового сообщения посетитель автоматически вернется в гостевую книгу, куда он только что добавил свое сообщение. Для того, чтобы это сделать, можно поместить в конец обработчика строчку

Header ("Location: имя_web-страницы_с_гостевой_книгой");

соответственно указав имя нужной страницы (например, передав его в форме вместе с остальными переменными - количеством отзывов и индексом страницы).

А еще лучше включить обработчик в сам файл niz.php, а в качестве страницы-обработчика формы указать ту же самую страницу с гостевой книгой (для этого достаточно просто не указывать никаких параметров в заголовке формы). В таком случае после отправки формы просто загрузится та же самая гостевая книга, но уже с добавленным новым сообщением. В результате весь код гостевой книги уместится в одном файле. Надо лишь с помощью оператора if предотвратить запуск обработчика в том случае, если странице никаких массивов с переменными не передано - иначе он будет запускаться и выдавать ошибки при простом заходе посетителя на страницу с гостевой книгой.


7. Код целиком

В каждую страницу, на которой располагается гостевая книга, следует включить такой сценарий:

<?php
$ind="код гостевой книги";
include ("niz.php");
?>

Файл niz.php должен содержать весь остальной код:

<?php
$dirct="gb";
if ($HTTP_POST_VARS['otziv']!="")
{
$otznam=$ind.time();
$hdl = fopen("$dirct/$otznam", "w+");
fwrite($hdl,nl2br(strip_tags($HTTP_POST_VARS['otziv'])));
fclose($hdl);
}
$hdl=opendir($dirct);
while (($file = readdir($hdl))!== false)
{
if (strstr($file, $ind)!=False)
    {
    $a[]=$file;
    }
}
closedir($hdl);
$l=sizeof($a);
if ($l!=0)
{
rsort($a);
foreach ($a as $value)
{
include ("$dirct/$value");
echo ("<br>(разделитель сообщений)<br>");
}
}
?>
<form method="post">
<textarea name="otziv" cols="60" rows="10" wrap="virtual"></textarea>
<input name="submit" type="submit" value="Добавить отзыв"></form>

В отличие от кода, разобранного выше, тут нет необходимости передавать в форме индекс гостевой книги, так как от все равно устанавливается на странице гостевой книги и тем самым доступен и на всем протяжении включенного в нее niz.php.


Рис.3. Внешний вид гостевой книги и файлов с ее сообщениями.

8. Улучшения

Бесспорно, данный код можно улучшать. Можно, например, сделать так, чтобы на странице отображались не сразу все сообщения, а лишь часть, скажем, последний десяток. Для этого следует немного изменить код вывода сообщений, вместо конструкции foreach использовав, скажем, оператор for в том случае, если количество сообщений больше десяти:

...
rsort($a);
if ($l>10)
   {
   for ($i = 0; $i < 10; $i++)
      {
      include ("$dirct/$a[$i]");
      echo ("<br>(разделитель сообщений) <br>");
      }
   }
else
   {
   foreach ($a as $k)
      {
      include ("$dirct/$k");
      echo ("<br>(разделитель сообщений) <br>");
      }
   }
}
...

Тогда на странице отобразятся лишь последние 10 сообщений.

Код, выводящий остальные сообщения так же, по десяткам, сделайте самостоятельно.


9. Заключение

Как видите, сделать гостевую книгу на PHP не просто, а очень просто. Весь ее сценарий уместится на одном экране даже на мониторах с небольшим разрешением.

Вместе с тем разобранный выше код является наиболее простым, но не самым оптимальным. При большом числе сообщений скорость работы программы вывода их на web-страницу сильно замедлится (хотя в какой-то степени может помочь разнесение сообщений разных гостевых книг по разным папкам). Поэтому в том случае, если гостевая книга перерастет статус простой книги отзывов, то имеет смысл подумать о другой схеме ее работы - с хранением всех сообщений в одном файле или, например, в базе данных MySQL. Но это уже другие темы.

Hosted by uCoz