Вложенные комментарии на PHP 


Вложенные комментарии на PHP

Опубликовано

Введение

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

При создании вложенных комментариев использовались следующие языки программирования и БД для хранения информации:

  • База данных 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;

 

Выполнение SQL запроса в оболочке phpMyAdmin

Рис. 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;}