Введение
Комментарии важная часть веб-сайта, предоставляющая посетителю возможность оценивать статью, указывать ошибки допущенные автором, а также делиться своим мнением, рекомендациями, отзывами. Вложенные комментарии позволяют получить структурированные отзывы, это очень удобно при их прочтении, кроме того создается визуальный эффект беседы аудитории веб-сайта. В данной статье будет рассмотрен способ создания вложенных комментариев.
При создании вложенных комментариев использовались следующие языки программирования и БД для хранения информации:
База данных MySQL –популярная БД, которую будем использовать для хранения комментариев;
- PHP;
- JS, JQuery;
- HTML;
- CSS;
База данных MySQL
Добавление в БД MySQL таблицы `comment`
Итак, приступим, первое, что необходимо сделать – это создать таблицу в БД MySQL, где будут храниться комментарии посетителей вашего веб-сайта и их персональная информация. Для этого необходимо выполнить нижеследующий запрос в БД, легче все это сделать через систему управления базами и таблицами phpMyAdmin, как показано на рисунке 1.
CREATE TABLE IF NOT EXISTS `comments` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`idparent` int(11) NOT NULL DEFAULT "0",
`user` varchar(255) CHARACTER SET cp1251 COLLATE cp1251_general_cs NOT NULL,
`email` varchar(255) CHARACTER SET cp1251 COLLATE cp1251_general_cs NOT NULL,
`message` text CHARACTER SET cp1251 COLLATE cp1251_general_cs NOT NULL,
`host` varchar(255) CHARACTER SET cp1251 COLLATE cp1251_general_cs NOT NULL,
`url` text CHARACTER SET cp1251 COLLATE cp1251_general_cs NOT NULL,
`date` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=cp1251 COLLATE=cp1251_general_cs AUTO_INCREMENT=1;

Рис. 1. Выполнение SQL запроса в оболочке phpMyAdmin.
Структура таблицы
Таблица `comments`:
Столбец `id` - уникальный идентификатор для каждого комментария;
Столбец `idparent` - необходим для определения типа комментария, т.е если комментарий является родительским, его `idparent`=’0’, если комментарий является дочерним, его `idparent`=’id’ родителя. Наглядно это можно проследить на рисунке 2;
Столбцы `user`, `email`, `message` - персональная информация пользователя, логин, почтовый адрес и сам комментарий;
В столбцах `host`, `url` - содержится информация об URL адресе комментария;

Рис 2. Иерархическая вложенность комментариев и их зависимости друг от друга.
Из рисунка 2 видно:
комментарии с `ID`=1 и 5, являются родительскими для всех остальных;
комментарии с `ID`=2 и 3 – дочерними для комментария с `ID`=1;
комментарий с `ID`=3, является родительским для комментария `ID`=4;
Легко видеть что, только родительскими могут быть первые в иерархии комментарии (`ID`=1 и 5), остальные комментарии, вложенные в них, могут быть как родительскими, так и дочерними.
Файловая структура
После добавления таблицы `comments`, дальнейшим шагом является создание скриптов, которые будут выполнять всю основную работу: добавление и запрос из БД, вывод вложенных комментарий на страницу документа. Для этих целей создадим следующие PHP файлы, каждый из которых будет отвечать за определенное действие:
Файл commentForm.php – форма, которую посетитель веб-сайта заполняет для добавления комментария;
Файл commentAdd.php – добавление новых комментарий в БД;
Файл commentView.php – вывод вложенных комментарий в документ;
Файл funcComment.php – файл содержит функции для работы с комментариями;
Кроме PHP должное внимание необходимо уделить и файлам, содержащим JavaScript функции и CSS стили:
Файл js/comments.js – файл содержит JS функции;
Файл css/comments.css – стилевой файл, оформление комментарий;
Дополнительные плагины, используемые при создании комментарий:
Файл js/jquery.js - JQuery библиотека;
Папка module/kcaptcha - captcha позволяет избежать спам комментарий;
Форма для добавления комментария
HTML фундамент формы. Файл commentForm.php
В файле commentForm.php создадим форму, которую посетитель будет заполнять, чтобы оставить комментарий.
<div id="CommentForm">
<form action="" method="POST" name="CommentFormSend">
<div class="header">
<div><h1>Добавить комментарий</h1></div>
<div><a href="#" title="Закрыть окно"><img src="imgs/close/1.png" border="0" /></a></div>
</div>
<br />
<br />
<div class="field">
<input name="commentUser" id="commentUser" value="Имя" onfocus="if(this.value == "" || this.value == "Имя"){this.value = ""}" onblur="if(this.value == ""){this.value = "Имя"}" />
<input name="commentEmail" id="commentEmail" value="E-mail" onfocus="if(this.value == "" || this.value == "E-mail"){this.value = ""}" onblur="if(this.value == ""){this.value = "E-mail"}" />
<br />
<br />
<textarea name="commentMessage" id="commentMessage"></textarea>
</div>
<br />
<div class="captcha">
<div>
<a href="#" title="Обновить код" onclick="document.getElementById("siimage").src = "module/kcaptcha/securimage_show.php?sid=" + Math.random(); return false">Обновить код</a>
<br />
<img id="siimage" src="module/kcaptcha/securimage_show.php?sid=7542a86d4b72c53a2dac88ffb767e290" />
</div>
<div>
<br />
<input type="text" name="commentCaptcha" id="commentCaptcha" />
</div>
<div>
<br />
<button onclick="return false;">Добавить</button>
</div>
</div>
<input type="hidden" name="CommentParent" id="CommentParent" value="0">
<?php
//Прошивка формы сессией
$_SESSION["unfmC"]=md5("Comment".date("l dS of F Y h:I:s A"));
?>
<input type="hidden" name="CommentSession" value="<?php print $_SESSION["unfmC"];?>">
</form>
</div>
Важные моменты в коде:
<div id="CommentForm"></div> - блок в котором будет размещена вся форма;
<div><a href="#" title="Закрыть окно"><img src="imgs/close/1.png" border="0" /></a></div> - при клике на изображение в верхнем правом углу, выполняется JQuery функция, которая скрывает окно формы, функция будет рассмотрена далее;
<div class="captcha"></div> - в данном блоке расположена captcha. Плагин находится в директории module/kcaptcha, используется для дополнительной защиты от спама;
<input type="hidden" name="CommentParent" id="CommentParent" value="0"> - скрытое поле, в которое помещается ID родителя если комментарий вложенный (дочерний), если комментарий сам является родителем то ID=0. При добавлении нового комментария в базу данные значение из скрытого поля CommentParent будут помещено в `idparent`;
<input type="hidden" name="CommentSession" value="<?php print $_SESSION["unfmC"];?>"> - скрытое поле, в которое размещается значение сессии, тем самым обеспечив еще одну защиту формы от подмены с помощью сессии.
JS функции для скрытия формы с экрана, и для проверки на незаполненные поля.
Файл js/comments.js
Фундамент формы готов, теперь необходимо добавить несколько функций на JS, чтобы обеспечить посетителя дополнительной информацией при заполнении формы для вложенных комментариев, и добавим возможность скрыть форму со страницы при необходимости. Разместим в файле js/comments.js следующие функции:
JQuery Функция для проверки на незаполненные поля. Выполняется в момент клика по кнопке “Добавить”, если поля заполнены корректно данные формы отправляются на обработку, иначе выдается сообщение, в котором указано, какие действия пользователь должен совершить для корректного заполнения формы;
JQuery Функция, скрывающая форму для добавления комментария с экрана, срабатывает при клике на изображении “Закрыть окно” в правом верхнем углу формы;
Java Script функция validateEmail(document.getElementById("commentEmail").value, проверяющая на корректность почтовый адрес введенный пользователем. Используется при проверке на незаполненные поля.
$(document).ready(function(){
//Проверка на незаполненные поля в форме
$("#CommentForm .captcha button").click(function(){
if (document.getElementById("commentUser").value=="" || document.getElementById("commentUser").value=="Имя"){alert("Заполните поле Имя");}
else if (document.getElementById("commentEmail").value=="" || document.getElementById("commentEmail").value=="E-mail"){alert("Заполните поле E-mail");}
else if (!validateEmail(document.getElementById("commentEmail").value)){alert("Некорректно введен E-mail");}
else if (document.getElementById("commentMessage").value==""){alert("Заполните поле Сообщение");}
else if (document.getElementById("commentCaptcha").value==""){alert("Введите код");}
else{document.forms.CommentFormSend.submit();}
});
//При клике на изображении “Закрыть окно”, скрывать форму.
$("#CommentForm .header div a").click(function(){
document.getElementById("CommentForm").style.display="none";
});
});
function validateEmail(email) {
//проверка введённого email
var reg = /^(([^<>()[].,;:s@"]+(.[^<>()[].,;:s@"]+)*)|(".+"))@(([[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}])|(([a-zA-Z-0-9]+.)+[a-zA-Z]{2,}))$/
if (!email.match(reg)) {
return (false);
}
return(true);
}
Оформление формы для добавления комментария CSS. Файл css/comments.css
#CommentForm{display:none;width:400px;font-family:Georgia;color:#808080;background-color:#F5F5F5;padding:10px;margin-bottom:10px;}
#CommentForm .header{}
#CommentForm .header > div{float:left;width:50%;text-align:right;}
#CommentForm .header > div h1{margin:0;padding:0;font:normal 15px "Georgia";text-align:left;}
#CommentForm .field {text-align:left;}
#CommentForm .field > input{width:48%;}
#CommentForm .field > textarea{width:98%;height:100px;}
#CommentForm .captcha{height:60px;}
#CommentForm .captcha > div{float:left;width:25%;text-align:center;}
#CommentForm .captcha > div img{height:35px;border:0}
#CommentForm .captcha > div input{width:70px;}
#CommentForm .captcha > div button{width:80px;}
#CommentForm .captcha > div a{font:11px "Georgia";color:#808080;text-decoration:none;}
#CommentForm .captcha > div a:hover{font:11px "Georgia";color:#000000;text-decoration:underline;}

Рис 3. Форма для добавления вложенных комментариев.
Добавление в БД новых вложенных комментарий
Обработка данных формы. Файл commentAdd.php
После создания формы для добавления комментарий, следующий шагом является создание файла, который будет вносить комментарии оставленные посетителями в БД, а точнее в таблицу `comments`.
<?php
//Проверка сессии
if ($_SESSION["unfmC"]==$_POST["CommentSession"] and !empty($_POST["CommentSession"])){
//Уничтожение сессии
unset($_SESSION["unfmC"]);
//Защита HTTP_REFERRER
if (!preg_match("/^(http://".$_SERVER["HTTP_HOST"]."/[a-zA-Z0-9-/]+)$/",$_SERVER["HTTP_REFERER"],$matches)){
print "Данные отправлены с другого домена";die;
}
//Captcha
include("module/kcaptcha/securimage.php");
$img = new Securimage();
$valid = $img->check($_POST["commentCaptcha"]);
if($valid) {
//Обработка данных формы. Замена HTML тегов на их сущности
$Message=htmlspecialchars($_POST["commentMessage"],ENT_QUOTES);
$User=htmlspecialchars($_POST["commentUser"],ENT_QUOTES);
$Email=htmlspecialchars($_POST["commentEmail"],ENT_QUOTES);
$Parent=intval($_POST["CommentParent"]);
$host=$_SERVER["HTTP_HOST"];
$url=$_SERVER["REQUEST_URI"];
//Добавление комментария в базу
funcCommentAdd($Parent,$User,$Email,$Message,$host,$url);
}else{
print "<script type="text/javascript">alert("Вы ввели неверный код");</script>";
}
}
?>
Функция добавления данных формы в БД funcCommentAdd(). Файл funcComment.php
После прохождения всех проверок, и преобразований переменных, выполняется функция funcCommentAdd(), которая собственно и добавляет комментарий пользователя в БД. Функция имеет следующий синтаксис:
funcCommentAdd($Parent,$User,$Email,$Message,$host,$url);
Функции расположена в файле funcComment.php и содержит код расположенный ниже:
function funcCommentAdd($idparent,
$user,
$email,
$message,
$host,
$url){
//Соединение с БД
$db = mysql_connect("localhost", "root", "");
mysql_select_db("forarticle");
mysql_query("SET NAMES CP1251");
//Запрос в БД
$query = " INSERT INTO `comments`
VALUES (
NULL,
"{$idparent}",
"{$user}",
"{$email}",
"{$message}",
"{$host}",
"{$url}",
NOW()
)";
$result = mysql_query($query);
mysql_close($db);
return $result;
}
Вывод вложенных комментарий на страницу
На данном этапе у нас есть форма для добавления комментариев, а также скрипт который заносит комментарии в Базу. Осталось вывести комментарии, находящиеся в Базе. Для этого создадим файл commentView.php, который и будет отвечать за вывод вложенных комментариев на страницу.
Схема формирования вложенных комментарий
Для начала хотел бы рассмотреть схему работы скрипта содержащегося в файле commentView.php, которая схематично показана на рисунке 4.
Рис. 4. Схема формирования вложенных комментарий.
Как показано на рисунке 4, происходит N-ое количество запросов комментарий из Базы, а точнее их количество равно степени вложенности комментарев + 1, т.е. если у вас четырехуровневая вложенность, то запросов будет 5. На первом уровне вложенности из базы запрашиваются все комментарии имеющие значение поля `idparent`=’0’ (родительские). Как видно из рисунка для первого запроса было найдено два комментария, `ID` которых равны 3 и 7. С первым уровнем покончено, теперь необходимо запросить из базы все дочерние комментарии, которые принадлежат родительским с `ID`=3;7. Поэтому следующим шагом будет запрос всех комментариев с `idparent`=’3’ и `idparent`=’7’, в результате запроса в базе было найдено три комментария с `ID`=10;12;13. Дальнейшие запросы будут проходить аналогично, пока не будут выведены все комментарии для данной страницы, либо цикл завершит свое выполнение в результате превышения лимита вложенности $commetMAX. А теперь перейдем к практической части.
Вывод всех комментарий. Файл commentView.php
<?php
//Ограничение по вложенности. В данном случает, не превышает 10 уровней
$commetMAX=10;
//Счетчик вложенности
$counter=-1;
//В массив $CommentID помещаются `id` комментариев выводимых на страницу.
//Содержимое данной переменной будет использоваться при запросах в БД.
$CommentID=array();
//Первый уровень будет всегда родительским, поэтому массив содержит один
//элемент, который равен 0 (родительский)
$CommentID[0]=0;
?>
<div id="CommentView">
<?php
//Выполнять цикл пока не будут выведены все комментарии,
//либо не будет превышен лимит ограничения по вложенности
WHILE ($counter<=$commetMAX){
$counter++;
//Запрос комментариев из БАЗЫ
$commentInfo=array();
$commentInfo=getComments($CommentID, $_SERVER["HTTP_HOST"], $_SERVER["REQUEST_URI"]);
//Если комментариев для вывода на страницу нет, то выходить из цикла
if (count($commentInfo)==0){break;}
$CommentID=array();
for ($i=0;$i<count($commentInfo);$i++){
//В массив $CommentID помещаются `ID` комментариев выводимых на страницу
$CommentID[$i]=$commentInfo[$i]["id"];
?>
<div id="CommentView_Parent<?php print $commentInfo[$i]["id"];?>">
<!--Каждый следующий уровень вложенности вдвигается на 20px вправо-->
<div style="padding-left:<?php print (20*$counter)?>px;">
<div class="Message">
<?php
print $commentInfo[$i]["message"];
?>
</div>
<div class="UserInfo">
<div>
<img src="imgs/comment/8.png" border="0" />
</div>
<div class="Username">
<?php
print "<b>{$commentInfo[$i]["user"]}</b><br />{$commentInfo[$i]["date"]}";
?>
</div>
<div>
<br />
<a href="#" title="Ответить на комментарий" onclick="CommentView_FormView("<?php print $commentInfo[$i]["id"];?>");return false;">Ответить</a>
</div>
</div>
<!--Контейнер для вставки в него формы добавления комментариев при клике на ссылку "Ответить"-->
<div id="CommentView_Form<?php print $commentInfo[$i]["id"];?>" style="padding-top:5px;clear:left;"></div>
</div>
<!--Контейнер для вставки в него вложенных (дочерних) комментариев-->
<div id="CommentView_Child<?php print $commentInfo[$i]["id"];?>"></div>
</div>
<?php
//JAVA SCRIPT функция переносит вложенный комментарий его родителю
if ($counter<>0){
print "<script type="text/javascript">CommentView_TeleportChild("{$commentInfo[$i]["idparent"]}","{$commentInfo[$i]["id"]}");</script>";
}
}
}
?>
<br />
<div>
<a href="#" onclick="CommentView_FormView("0");return false;">Добавить комментарий</a>
</div>
<!--Контейнер для вставки в него формы добавления комментариев при клике на ссылку "Добавить комментарий"-->
<div id="CommentView_Form0"></div>
</div>
Основные DIV блоки в вышеуказанном скрипте:
<div id="CommentView"> - основной блок, в который помещаются все вложенные комментарии оставленные посетителями сайта, отображаемые на странице;
<div id="CommentView_Form+ID"></div> : где ID = 0 или php переменная $commentInfo[$i]["id"] содержащая уникальный ID текущего комментария. При клике по ссылке “Добавить комментарий” (родительские комментарии) или “Ответить” (дочерние комментарии), в блок внедряется форма для добавления комментария пользователем. При этом скрытому полю <input type="hidden" name="CommentParent"> находящемуся в файле commentForm.php присваивается ID (0 или $commentInfo[$i]["id"]) текущего комментария. Как было рассмотрено выше содержимое данного скрытого поля заносится в БД в поле `idparent`;
<div id="CommentView_Parent+ID"></div> : где ID – php переменная $commentInfo[$i]["id"], содержащая уникальный ID текущего комментария. Блок хранит одни комментарий (родительский), важным моментом является содержание в нем блока <div id="CommentView_Child+ID">, в который можно поместить дочерний комментарий, смотрите следующий пункт;
<div id="CommentView_Child+ID "></div> : где ID – php переменная $commentInfo[$i]["id"], содержащая уникальный ID текущего комментария. Данный блок необходим для внедрения в него блока <div id="CommentView_Parent+ID_CHILD"></div>, если он является дочерним комментарием для <div id=" CommentView_Parent+ID "></div>. Чтобы ясней понять суть, давайте рассмотрим рисунок 5. Внедрение происходит с помощью JS функции CommentView_TeleportChild(idparent,id); которая будет рассмотрена ниже;

Рисунок 5. Вставка дочерних блоков с комментариями в родительские или построение вложенных комментариев.
На рисунке 5 показано как происходит построение вложенных комментарий. Сначала формируются корневые комментарии (всегда родительские). Затем в них вставляются дочерние комментарии: происходит это следующим образом, PHP скрипт создает блок с комментарием, а затем за дело берется JS функция CommentView_TeleportChild(), которая и вставляет только что созданный дочерний комментарий в место, где он должен находиться.
PHP функция для запроса вложенных комментарий из базы getComments(). Файл funcComment.php
Функция необходима для запроса из БД комментарий по заданным параметрам:
$commentInfo=getComments($CommentID,$host,$url);
$CommentID – массив, содержащий ID родителей, по которым будут найдены все дочерние комментарии. Первый уровень вложенности имеет только одного родителя ID которого равен 0;
$host – доменное имя, необходимо для поиска комментарий принадлежащих данному домену;
$url – URL страницы, наряду с доменным именем $host, участвует в поиске вложенных комментарий в базе;
<?php
function getComments($CommentID,$host,$url){
//Соединение с БД
$db = mysql_connect("localhost", "root", "");
mysql_select_db("forarticle");
mysql_query("SET NAMES CP1251");
//==================================================================
$host=str_replace("www.","",mysql_real_escape_string($host));
$url=mysql_real_escape_string($url);
//Формирования строки для запроса
//Сборка ID всех родителей для нахождения по ним дочерних комментариев
$query=" and (";
for ($i=0;$i<count($CommentID);$i++){
$CommentID[$i]=intval($CommentID[$i]);
if ($i==0){$query.=" `idparent`="{$CommentID[$i]}" ";}
else{$query.=" or `idparent`="{$CommentID[$i]}" ";}
}
$query.=" )";
//==================================================================
$query="Select *
From `comments`
Where `host` LIKE "%{$host}" and
`url`="{$url}"
{$query}
Order by `date` DESC";
$result = mysql_query($query);
$result_row=array();
$kol=-1;
while ($row = mysql_fetch_array($result,MYSQL_BOTH)) {
$kol++;
$result_row[$kol]["id"]=$row["id"];
$result_row[$kol]["idparent"]=$row["idparent"];
$result_row[$kol]["user"]=$row["user"];
$result_row[$kol]["email"]=$row["email"];
$result_row[$kol]["message"]=$row["message"];
$result_row[$kol]["date"]=$row["date"];
}
mysql_close($db);
return $result_row;
}
?>
JS функция для вставки одного DIV блока в другой CommentView_TeleportChild(). Файл js/comments.js
//Функция внедряет дочерний блок с комментариями в родительский
function CommentView_TeleportChild(parent,id){
document.getElementById("CommentView_Child"+parent) .appendChild(document.getElementById("CommentView_Parent"+id));
}
С помощью метода .appendChild() используемого в данной функции, происходит распределение дочерних блоков, как показано на рисунке 5.
JS функция вставляет форму для добавления вложенных комментарий в указанный DIV блок CommentView_FormView(). Файл js/comments.js
//Отобразить Форму в нужном месте и задать скрытому полю CommentParent ID родительского комментария
function CommentView_FormView(parent){
document.getElementById("CommentView_Form"+parent) .appendChild(document.getElementById("CommentForm"));
document.getElementById("CommentForm").style.display="block";
document.getElementById("CommentParent").value=parent;
}
Функция внедряет форму для добавления комментарий в блок, где пользователь кликнул на ссылку “Ответить” либо на ссылку “Добавить комментарий”, а скрытому полю document.getElementById("CommentParent").value присваивается ID этого блока, в дальнейшем при добавлении комментария в базу данные значение скрытого поля document.getElementById("CommentParent").value будет присвоено полю `idparent`.
Оформление выводимых комментариев на страницу CSS. Файл css/comments.css
#CommentView .Message{padding:10px;background-color:#F5F5F5;border:1px solid #EAEAEA;font:12px "Georgia";color:#737373;}
#CommentView .UserInfo > div{float:left;text-align:left;font:normal 11px "Tahoma";color:#000000;}
#CommentView .UserInfo .Username{width:150px;padding-left:5px;padding-right:5px;}
#CommentView .UserInfo a{font:11px "Georgia";color:#3878BA;text-decoration:none;}
#CommentView .UserInfo a:hover{font:11px "Georgia";color:#3878BA;text-decoration:underline;}
#CommentView > div a{font:18px "Georgia";color:#000000;text-decoration:none;}
#CommentView > div a:hover{font:18px "Georgia";color:#000000;text-decoration:underline;}