Создаем гостевую книгу у себя на сайте
Недавно получил письмо на email с просьбой помочь разобраться со скриптом гостевой книги или книги отзывов. Поэтому выполняю свое обещание и сегодняшняя статья будет именно на эту тему.
Что такое гостевая книга и зачем она вообще нужна на сайте?
Гостевая книга — это своего рода книга жалоб или пожеланий, где любой посетитель Вашего сайта может оставить свое сообщение, которое (в случае одобрения админом) смогут прочитать все желающие. Т.е. это самые обычные комментарии, только не к отдельной заметки, а к целому сайту!
Гостевая книга добавляет интерактивность на Ваш сайт и является распространенным способом обратной связи.
Установив скрипт книги отзывов на свой сайт, Вы сможете увидеть общее впечатление пользователей о Вашем сайте и сделать определенные выводы для себя!
Посмотреть рабочий пример гостевой книги можно здесь.
Итак, для начала создадим табличку в базе данных mysql, где будут храниться все комментарии пользователей:
CREATE TABLE IF NOT EXISTS `guestbook` ( `id` int(11) NOT NULL auto_increment, `user_ip` int(10) unsigned NOT NULL, `user_email` varchar(50) NOT NULL, `addtime` int(11) NOT NULL, `name` varchar(15) NOT NULL, `text` text NOT NULL, `admin_text` text NOT NULL, `image` varchar(40) NOT NULL, `sex` tinyint(1) NOT NULL default '1', PRIMARY KEY (`id`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;
где:
- id — порядковый номер сообщения
- user_ip — IP-адрес пользователя, представленный в виде целого цисла
- user_email — email-адрес пользователя
- addtime — время добавления сообщения
- name - имя пользователя
- text — текст сообщения
- admin_text — текст ответа администратора на сообщение
- image — аватар пользователя
- sex — пол пользователя (мужской/женский)
Как Вы видите в табличке есть поле для IP-адреса пользователя. Это делается для того, чтобы можно было потом сделать черный список, куда можно заносить IP-адреса «не хороших» пользователей, которые в дальнейшем не смогут оставлять сообщения.
Поле sex нужно для того, чтобы выводить «правильный» аватар, в случае, когда пользователей не загрузит свой.
С базой данных разобрались. Переходим к программированию. Так как мы работаем с базой данных, то первым делом создадим самый простой класс для работы с базой данных. Для этого создадим файл DB.class.php и поместим туда следующий код:
class DB { private static $instance; private $MySQLi; private function __construct(array $dbOptions){ $this->MySQLi = @ new mysqli($dbOptions['db_host'],$dbOptions['db_user'], $dbOptions['db_pass'],$dbOptions['db_name'] ); if (mysqli_connect_errno()) { throw new Exception('Ошибка базы данных.'); } $this->MySQLi->set_charset("utf8"); } public static function init(array $dbOptions){ if(self::$instance instanceof self){ return false; } self::$instance = new self($dbOptions); } public static function getMySQLiObject(){ return self::$instance->MySQLi; } public static function query($q){ return self::$instance->MySQLi->query($q); } public static function esc($str){ return self::$instance->MySQLi->real_escape_string(htmlspecialchars($str)); } }
Стоит сказать, что конструктор этого класса объявлен как private, таким образом, объект не может быть создан вне пределов класса, и инициализация возможна только из статического метода init(). Он берет массив с параметрами соединения с MySQL и создает экземпляр класса, который содержится в статической переменной self::$instance. Таким образом, обеспечивается существование единственного соединения с базой данных в конкретный момент времени.
Остальная часть класса, выполняет запросы к базе данных, на основе статического метода query().
При желании Вы можете доработать этот класс, так как Вам это нужно!
Также в разработке гостевой книги нам понадобятся вспомогательные функции, которые я вынесу в отдельный файл и назову его helper.php.
Теперь мы плавно подошли к самому главному файлу в нашем скрипте — index.php. Именно здесь и будет выполняться вся логика скрипта.
Итак, первым делом необходимо инициализировать сессию, задать основные настройки и выполнить подключение к базе данных mysql. В сессии будет храниться защитный код (капча) формы.
session_start(); /* Конфигурация базы данных. Добавьте свои данные */ $dbOptions = array( 'db_host' => 'localhost', 'db_user' => '', 'db_pass' => '', 'db_name' => '' ); //Подключаем класс для работы с базой данных require "DB.class.php" //Подключаем вспомогательные функции require "helper.php" // Соединение с базой данных DB::init($dbOptions); $appath = realpath(dirname(__FILE__)).'/'; //Папка на сервере, куда будут загружаться аватарки $uploaddir = 'images/avatars'; //Максимальное число сообщений на одной странице $per_page = 10; //Число страниц в пейджинге $num_page = 2;
Для построения навигации по страницам в гостевой книге, необходимо узнать общее число сообщений. Это можно сделать так:
//Получаем общее число сообщений $result = DB::query('SELECT COUNT(*) AS numrows FROM guestbook'); $total = $result->fetch_object()->numrows;
Теперь определим номер страницы, которую необходимо показать. Для этого обработаем переменную $_GET['p']
$start_row = (!empty($_GET['p']))? intval($_GET['p']): 0; if($start_row < 0) $start_row = 0; if($start_row > $total) $start_row = $total;
Далее получаем список сообщений:
$result = DB::query('SELECT * FROM guestbook ORDER BY addtime DESC LIMIT '.$start_row.','.$per_page); //Здесь будет храниться список сообщений $items = array(); while($row = $result->fetch_assoc()){ $row['addtime'] = format_date($row['addtime'],'date').'|'.format_date($row['addtime'],'time'); $items[] = $row; }
Здесь я использовал функцию format_date() для работы с датой и временем, которую я создал в файле helper.php. Основная её задача — это вывод даты и времени в русском формате. Вот ее код:
function format_date($date,$format = 'date'){ if(empty($date)) return ''; $months = array( '1' => 'января', '2' => 'февраля', '3' => 'марта', '4' => 'апреля', '5' => 'мая', '6' => 'июня', '7' => 'июля', '8' => 'августа', '9' => 'сентября', '10' => 'октября', '11' => 'ноября', '12' => 'декабря' ); if($format == 'time'){ return date('H:i',$date); } elseif($format == 'date'){ $m = date('n', $date); $m = $months[$m]; $d = date('j',$date); $y = date('Y',$date); return $d.' '.$m.' '.$y; } else{ return date('d.M.Y H:i',$date); } }
Данная функция имеет всего 2 параметра:
- $date — дата в формате UNIX (количество секунд пройденных с ночи 1-ого января 1970 - ого года)
- $format — форма вывода даты.
Теперь мы можешь вывести список сообщений на странице. Для этого я использую следующий html-код:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" > <html xmlns="http://www.w3.org/1999/xhtml" id="nojs"> <head> <meta http-equiv="keywords" content="Гостевая книга" /> <meta http-equiv="description" content="Гостевая книга" /> <title>Гостевая книга</title> <link rel="stylesheet" href="styles/style.css" type="text/css" /> <script type="text/javascript" src="js/jquery-1.4.2.min.js"></script> <script type="text/javascript" src="js/scripts.js"></script> </head> <body> <div class="contentToChange"> <h1>Отзывы</h1> <a name="top"></a> <div class="noFloat"> <div class="titleText" onclick="show_form()">оставить отзыв <a class="add_com_but"><img src="images/show_com.png" alt=""></a> </div> </div> <div class="comments-block"> <?php if(!empty($items)):foreach($items as $item): ?> <a name="comments-<?=$item['id']?>"></a> <div class="com-item-pad" id="com_<?=$item['id']?>"> <div class="com-item"> <?=show_avatar($uploaddir,$item['image'],$item['sex']);?> <div class="user_info">> <div class="info_panel"> <div class="fl-left"> <strong><?=$item['name']?></strong> <span class="date"><?=$item['addtime']?></span> </div> </div> <div class="com_body"><?=$item['text']?></div> </div> </div> </div> <div id="com-form-wrap"></div> <?php endforeach; else:?> <div class="com-item"><h2>На данный момент нет активных отзывов!</h2></div> <?php endif;?> </div> <?=pagination($total,$per_page,$num_page,$start_row,'/demo/guestbook')?> </div> </body> </html>
Здесь я использовал 2 новых функции: show_avatar() и pagination(), которые я создал и поместил в файл helpers.php
Функция pagination() формирует навигацию по страницам, о которой я уже писал, поэтому на этом я останавливаться не буду.
Функция show_avatar() выводит аватарку пользователя. Вот ей код:
function show_avatar($uploaddir,$filename,$sex = 1){ if(!empty($uploaddir) && !empty($filename)){ return '<img src="'.$uploaddir.'/'.$filename.'" alt="" class="ava" align="left">'; } elseif(!empty($sex) && $sex == 2){ return '<img src="images/no_image_female.png" alt="" class="ava" align="left">'; } else{ return '<img src="images/no_image_male.png" alt="" class="ava" align="left">'; } }
Она принимает 3 параметра:
$uploaddir — директория, где храняться аватарки
$filename — название файла аватарки
$sex — пол пользователя.
Теперь создадим форму для добавления комментариев:
<div class="add_com_block" id="add_com_block" style="display:<?=(!empty($errors))? 'block': 'none'?>;"> <?=(!empty($errors))? '<div class="errors">'.implode($errors).'</div>': ''?> <form action="index.php" method="post" accept-charset="utf-8" enctype="multipart/form-data"> <label>Представьтесь:</label> <input class="text" name="name" value="<?=set_value('name');?>" type="text"> <label>Ваш e-mail:</label> <input class="text" name="user_email" value="<?=set_value('user_email');?>" type="text"> <label>Сообщение:</label> <textarea cols="15" rows="5" name="text" id="com_text"><?=set_value('text');?></textarea> <label>Аватар:</label> <input class="file" name="image" type="file"> <div class="radios"> <label for="sex1">Мужчина: </label><input name="sex" id="sex1" class="radio" value="1" checked="checked" type="radio"> <label for="sex2">Женщина: </label><input name="sex" id="sex2" class="radio" value="2" type="radio"> </div> <label>Введите цифры:</label> <div class="plusClear mb plusOverflow"> <?php require 'captcha.php';?> <input class="capch" name="keystring" value="" maxlength="6" style="font-size: 16pt;" type="text"> </div> <div class="plusClear"><input class="but" name="submit" value="Отправить" type="submit"></div> <input name="email" value="" type="hidden"> <input name="form" value="guestbook" type="hidden"> <img class="hide_com" src="images/hide_com.gif" alt="" onclick="show_form();"> </form> </div>
По умолчанию наша форма не видна на страница. Она появляется только при клике на кнопку «оставить отзыв». Все данные форма передает файлу index.php методом POST.
Для установки переданных данных в форму я использовал функцию set_value(), которую я создал и поместил в файл helper.php.
function set_value($name,$default = ''){ return (!empty($_POST[$name]))? trim(htmlspecialchars($_POST[$name])): $default; }
Данная функция имеет всего два параметра:
1. $name - Имя переменной в массиве $_POST
2. $default — значение по умолчанию
Если данные переданы методом POST и заданная переменная существует, то выводи её, предварительно удалив пробелы и преобразовав спецсимволы в их html-сущности. Иначе выводим значение по умолчанию.
Это самая минимальная проверка пользовательских данных. При желании можно расширить эту функцию!
Давайте теперь напишем обработчик формы, который и будет добавлять комментарии в базу данных.
Для этого в index.php добавим следующий код:
//Если нажата кнопка "Добавить отзыв" if(!empty($_POST['submit'])){ $now = time(); $antiflood = 120;//Время в секундах для блокировки повторной отправки сообщения $errors = array(); $name = (!empty($_POST['name'])) ? trim(strip_tags($_POST['name'])) : false; $user_email = (!empty($_POST['user_email']) && filter_var($_POST['user_email'], FILTER_VALIDATE_EMAIL)) ? $_POST['user_email'] : false; $text = (!empty($_POST['text'])) ? trim(strip_tags($_POST['text'])) : false; $sex = (!empty($_POST['sex'])) ? intval($_POST['sex']) : 1; $keystring = (!empty($_POST['keystring'])) ? $_POST['keystring'] : false; // ANTIFLOOD if (!$antiflood || (!isset($_SESSION['time']) || $now - $antiflood >= $_SESSION['time']) ) { if (empty($name)) $errors[] = '<div class="error">Вы не заполнили поле "Представьтесь"!</div>'; if (empty($user_email)) $errors[] = '<div class="error">Вы не корректно заполнили поле "Ваш e-mail"!</div>'; if (empty($text)) $errors[] = '<div class="error">Вы не заполнили поле "Текст"!</div>'; if (!$keystring || $keystring != $_SESSION['keystring']) $errors[] = '<div class="error">Вы не правильно ввели цифры с картинки!</div>'; if (!empty($_FILES['image']['tmp_name'])) { $tmp_name = $_FILES['image']['tmp_name']; $file_mime = $_FILES['image']['type']; list($m1, $m2) = explode('/', $file_mime); if ($m1 == 'image') { $file_ext = strtolower(strrchr($_FILES['image']['name'],'.')); // получаем расширение файла $file_name = uniqid(rand(9999,100000));// генерим уникальное имя $avatar = $file_name.$file_ext; if (move_uploaded_file($tmp_name, $appath.$uploaddir.'/'.$avatar)) { chmod($appath.$uploaddir.'/'.$avatar, 0666); } } } $avatar = (!empty($avatar))? $avatar: ''; //Если ошибок нет пишем отзыв в базу if(!$errors){ //Переводим IP адрес пользователя в безнаковое целое число $user_ip = sprintf("%u", ip2long($_SERVER['REMOTE_ADDR'])); DB::query("INSERT INTO site_guestbook (name,user_email,text,sex,image,addtime,user_ip) VALUES ('".DB::esc($name)."','".DB::esc($user_email)."','".DB::esc($text)."','".DB::esc($sex)."','".DB::esc($avatar)."','".$now."','".$user_ip."')"); $_SESSION['time'] = $now; unset($_SESSION['keystring']);//Удаляем капчу из сессии if(DB::getMySQLiObject()->affected_rows == 1){ $errors[] = '<div class="error">Ваш отзыв успешно добавлен!</div>'; } else{ $errors[] = '<div class="error">Ваш отзыв не добавлен. Попробуйте позже!</div>'; } } } else{ $errors[] = '<div class="error">Подождите '.ceil($antiflood/60).' минут(у,ы) перед отправкой следующего сообщения!</div>'; } }
Помимо проверки самой капчи, я использовал еще один прием защиты от спама. Пользователь не сможет отправить больше одного сообщения, пока не пройдет 3 минуты! Для этого я записываю в сессию время добавления комментария и при следующем добавлении проверяю это время. Если уже прошло 120 секунд, то добавляем сообщение иначе выводим сообщение об ошибке.
if (!$antiflood || (!isset($_SESSION['time']) || $now - $antiflood >= $_SESSION['time']) ) { Все ОК. Идем дальше... } else{ $errors[] = '<div class="error">Подождите '.ceil($antiflood/60).' минут(у,ы) перед отправкой следующего сообщения!</div>'; }
Так же идет проверка на заполнение полей, правильность ввода капчи и загрузка аватарки на сервер. О том как загружать файлы на сервер можно почитать тут, тут и тут. В этой статье я не показываю, как обрабатывать и нарезать аватарки, поэтому выводится оригинал файла. Имейте это ввиду!
Если ошибок нет, то переводим IP адрес пользователя в беззнаковое целое число и записываем в базу данных.
При создании формы для добавления отзыва я использовал капчу, о которой писал здесь, поэтому на ней не буду останавливаться.
Осталось написать javascript-код, который будет показывать форму добавления отзыва при клике на кнопку «Оставить отзыв». Если Вы внимательно читали эту статью, то должны были заметить, что для кнопки «Оставить отзыв» стоит обработчик onclick="show_form()". т. е. при клике на неё вызывается javascript-функция show_form(), написанием которой мы сейчас и займемся!
function show_form(){ var form_add = $('div.add_com_block'); if(form_add.css('display') == 'none'){ form_add.slideDown(400); $('.add_com_but').hide(); } else{ form_add.slideUp(400); $('.add_com_but').show(); } }
Здесь все очень просто, при вызове этой функции мы проверяем, отображается ли в данный момент форма для добавления отзыва, если да, то мы её прячем, иначе показываем!
Стили приводить не буду, так как итак, статья получилась очень большой. Все Вы найдете в архиве с иходниками, которые можно скачать тут.
На этом все! Удачи! Оставляйте свои отзывы в комментариях!
-
Комментарии (40)
- Сайт