Free

PHP. Разработка модуля комментариев для сайта

Text
Mark as finished
Font:Smaller АаLarger Aa

19. Регистрация пользователей

https://ruseller.com/lessons.php?rub_id=37&id=350

Для регистрации пользователей используются файлы:

• Страница с формой регистрации «form_registration.html».

• Страница сохранения пользователя «save_user.php».

• Страница активации пользователя «activation.php».

• Капча

Листинг 35. form_registration.html Путь: news/chat/admin/users/ form_registration.html

<!DOCTYPE html>

<html>

<head>

<link rel="stylesheet" type="text/css" href="/chat/style.css" />

<title>Регистрация на сайте</title>

</head>

<body class="chatbody">

<h2>Регистрация на сайте</h2>

<form

class="chatform"

action="save_user.php"

method="post"

enctype="multipart/form-data"

id="reg"

>

<fieldset class="chatfieldset">

<legend>Данные для авторизации</legend>

<label

>Ваш логин *: <input name="login" type="text" size="15" maxlength="15"

/></label>

<hr />

<label

>Ваш пароль *:

<input name="password" type="password" size="15" maxlength="15"

/></label>

<hr />

<label

>Ваш E-mail *:

<input name="email" type="email" size="15" maxlength="100"

/></label>

<br />

</fieldset>

<br />

<fieldset class="chatfieldset">

<legend>Выберите аватар</legend>

<label>

Изображение должно быть квадратной формы<br />

и иметь расширение jpg, gif или png:<br />

<br />

<input type="FILE" name="fupload"

/></label>

</fieldset>

<br />

<!– В переменную fupload отправится изображение, которое выбрал пользователь. –>

<fieldset class="chatfieldset">

<legend>Введите код с картинки *:</legend>

<input type="text" name="capcha" />

<div><img src="/chat/capcha/captcha.php" /></div>

</fieldset>

<p>

<input type="submit" name="submit" value="Зарегистрироваться" />

<!– Кнопочка (type="submit") отправляет данные на страничку save_user.php –>

</p>

</form>

Звездочками (*) обозначены поля, обязательные для заполнения.

<p><a href="/../">Вернуться</a></p>

</body>

</html>

Выводит страницу «Регистрация на сайте» и форму регистрации. В форме предусмотрена защита при помощи «капчи» и предлагается отправка для регистрации следующих данных: «Логин», «email», «Пароль», «Аватар».

20. Капча

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

Листинг 36. captcha.php Путь:chat\capcha\captcha.php

<?php

if (session_id() == '') {

session_start();

}

$string = "";

for ($i = 0; $i < 5; $i++) {

$string .= chr(rand(97, 122));

}

$_SESSION['rand_code'] = $string;

$dir = $_SERVER['DOCUMENT_ROOT'] . '/chat/capcha/fonts/verdana.ttf';

$image = @imagecreatetruecolor(170, 60) or die('Невозможно инициализировать GD поток'); // Создаём изображение

$black = imagecolorallocate($image, 0, 0, 0);

$color = imagecolorallocate($image, 200, 100, 90);

$white = imagecolorallocate($image, 255, 255, 255);

imagefilledrectangle($image, 0, 0, 399, 99, $white);

imagettftext($image, 30, 0, 10, 40, $color, $dir, $_SESSION['rand_code']);

header("Content-type: image/png");

imagepng($image);

Здесь в цикле генерируется строка из случайных чисел, при этом числа сразу преобразуются в символьные эквиваленты. Диапазон чисел 97-122 соответствует буквам латинского алфавита в диапазоне a-z по ASCII http://www.asciitable.com/ . Полученный набор букв в виде строки сохраняется в переменную сессии, а затем оформляется в виде изображения.

21. Страница сохранения пользователя

За сохранение и обработку полученных при регистрации данных пользователя отвечает файл «save_user.php»

Листинг 37. save_user.php Путь: news/chat/admin/users/ save_user.php

<?php

error_reporting(E_ALL);

if (session_id() == '') {

session_start();

}

/* заносим введенный пользователем логин в переменную $login, если он пустой, то уничтожаем переменную */

if (isset($_POST['login'])) {

$login = $_POST['login'];

if (mb_strlen($login) < 2 or mb_strlen($login) > 15) { //проверяем длину логина

exit("Логин должен состоять не менее чем из 3 символов и не более чем из 15."); //останавливаем выполнение сценариев

}

if ($login == '') {

unset($login);

}

}

/* заносим введенный пользователем пароль в переменную $password, если он пустой, то уничтожаем переменную */

if (isset($_POST['password'])) {

$password = $_POST['password'];

if (mb_strlen($password) < 3 or mb_strlen($password) > 15) { //проверяем длину пароля

exit("Пароль должен состоять не менее чем из 3 символов и не более чем из 15."); //останавливаем выполнение сценариев

}

if ($password == '') {

unset($password);

}

}

/* заносим введенный пользователем код в переменную $capcha, если он пустой, то уничтожаем переменную */

if (isset($_POST['capcha'])) {

$capcha = $_POST['capcha'];

if ($capcha == '') {

unset($capcha);

}

}

/* заносим введенный пользователем e-mail, если он пустой, то уничтожаем переменную */

if (isset($_POST['email'])) {

$email = $_POST['email'];

if ($email == '') {

unset($email);

}

}

$capcha = stripslashes($capcha); //sequrity чистим капчу

$capcha = htmlspecialchars($capcha);

$capcha = trim($capcha); //удаляем лишние пробелы

if (($capcha == $_SESSION["rand_code"]) && ($capcha != "")) { //проверяем капчу

} else {

exit("Капча введена неправильно");

}

if (empty($login) or empty($password) or empty($email) or empty($capcha)) { //если пользователь не ввел данные, то выдаем ошибку и останавливаем скрипт

exit("Вы ввели не всю информацию, вернитесь назад и заполните все поля!");

}

if (!preg_match("/[0-9a-z_]+@[0-9a-z_^\.]+\.[a-z]{2,3}/i", $email)) { //проверка е-mail адреса регулярными выражениями на корректность

exit("Неверно введен е-mail!");

}

$login = stripslashes($login); //если логин и пароль введены, то обрабатываем их, чтобы теги и скрипты не работали, мало ли что люди могут ввести

$login = htmlspecialchars($login);

$password = stripslashes($password);

$password = htmlspecialchars($password);

$login = trim($login); //удаляем лишние пробелы

$password = trim($password);

/*......................Шифруем пароль...........................*/

//можно добавить несколько своих символов по вкусу, например, вписав "she".

//Если этот пароль будут взламывать методом подбора у себя на сервере этой же md5,то явно ничего хорошего не выйдет.

//Но советую ставить другие символы, можно в начале строки или в середине.

//При этом необходимо увеличить длину поля password в базе. Зашифрованный пароль может получится гораздо большего размера.

$password = md5($_POST['password'] . 'swl'); //шифруем пароль

include $_SERVER['DOCUMENT_ROOT'] . '/chat/dsn.php'; // подключаемся к базе

$sth = $dsn->prepare("SELECT id FROM users WHERE login=:login"); // проверка на существование пользователя с таким же логином

$sth->execute(array(':login' => $login));

if (!empty($sth->fetch(PDO::FETCH_ASSOC))) {

exit("Извините, введённый вами логин уже зарегистрирован. Введите другой логин.");

}

/*....................avatar................................*/

if (empty($_FILES['fupload']['name'])) {

//если переменной не существует (пользователь не отправил изображение),то присваиваем ему заранее приготовленную картинку с надписью "нет аватара"

$avatar = "avatars/noavatar.png"; //можете нарисовать net-avatara.jpg или взять в исходниках

} else {

//иначе – загружаем изображение пользователя

$path_to_90_directory = 'avatars/'; //папка, куда будет загружаться начальная картинка и ее сжатая копия

/* проверка формата исходного изображения */

if (preg_match('/[.](JPG)|(jpg)|(gif)|(GIF)|(png)|(PNG)$/', $_FILES['fupload']['name'])) {

$filename = $_FILES['fupload']['name'];

$source = $_FILES['fupload']['tmp_name'];

$target = $path_to_90_directory . $filename;

move_uploaded_file($source, $target); //загрузка оригинала в папку $path_to_90_directory

if (preg_match('/[.](GIF)|(gif)$/', $filename)) {

$im = imagecreatefromgif($path_to_90_directory . $filename); //если оригинал был в формате gif, то создаем изображение в этом же формате. Необходимо для последующего сжатия

}

if (preg_match('/[.](PNG)|(png)$/', $filename)) {

$im = imagecreatefrompng($path_to_90_directory . $filename); //если оригинал был в формате png, то создаем изображение в этом же формате. Необходимо для последующего сжатия

}

if (preg_match('/[.](JPG)|(jpg)|(jpeg)|(JPEG)$/', $filename)) {

$im = imagecreatefromjpeg($path_to_90_directory . $filename); //если оригинал был в формате jpg, то создаем изображение в этом же формате. Необходимо для последующего сжатия

 

}

// Создание квадрата 90x90

// dest – результирующее изображение

// w – ширина изображения

// ratio – коэффициент пропорциональности

$w = 90; // квадратная 90x90. Можно поставить и другой размер.

$h = 90;

// создаём исходное изображение на основе

// исходного файла и определяем его размеры

$w_src = imagesx($im); //вычисляем ширину

$h_src = imagesy($im); //вычисляем высоту изображения

if ($w_src !== $h_src) {

exit('<h4>Стороны изображения для загрузки должны быть равны. Например 256*256.<br>Рекомендую использовать готовые аватары со специализированных сайтов.<br>

Или подготовьте картинку в графическом редакторе</h4><br><i>p.s. грузим аватары, а не картины</i>');

}

// создаём пустую квадратную картинку

// важно именно truecolor!, иначе будем иметь 8-битный результат

$dest = imagecreatetruecolor($w, $w);

$white = imagecolorallocate($dest, 255, 255, 255);

imagefill($dest, 0, 0, $white);

imagecopyresampled($dest, $im, 0, 0, 0, 0, $w, $w, $w_src, $h_src);

$date = time(); //вычисляем время в настоящий момент.

imagejpeg($dest, $path_to_90_directory . $date . ".jpg"); //сохраняем изображение формата jpg в нужную папку, именем будет текущее время. Сделано, чтобы у аватаров не было одинаковых имен.

$avatar = $path_to_90_directory . $date . ".jpg"; //заносим в переменную путь до аватара.

$delfull = $path_to_90_directory . $filename;

unlink($delfull); //удаляем оригинал загруженного изображения, он нам больше не нужен. Задачей было – получить миниатюру.

} else {

//в случае несоответствия формата, выдаем соответствующее сообщение

exit("Аватар должен быть в формате <strong>JPG,GIF или PNG</strong>"); //останавливаем выполнение сценариев

}

//конец процесса загрузки и присвоения переменной $avatar адреса загруженной авы

}

/*.........................Сохраняем пользователя в базу......................*/

if (!isset($date)) {

$date = time();

}

try {

$sql = "INSERT INTO users (login,password,email,img,date) VALUES(:login,:password,:email,:img, :date)";

$result2 = $dsn->prepare($sql);

$result2->execute([

'login' => $login,

'password' => $password,

'email' => $email,

'img' => $avatar,

'date' => $date

]);

$role = 'user';

$authorid = $dsn->lastInsertId();

$sql = "INSERT INTO authorrole (authorid,roleid) VALUE (:authorid,:roleid)";

$resultrole = $dsn->prepare($sql);

$resultrole->execute([

'authorid' => $authorid,

'roleid' => $role

]);

echo '<img src="' . $avatar . '">';

echo ' ' . '<h3>' . $login . '</h3>' . ' ' . "Вы успешно зарегистрированы! <a href='/index.html'>Главная страница</a>";

} catch (PDOException $e) {

echo "You have an error: " . $e->getMessage() . "<br>";

echo "On line: " . $e->getLine();

}

$activation = md5($authorid) . md5($login);

$subject = "Подтверждение регистрации"; //тема сообщения

$message = "Здравствуйте! Спасибо за регистрацию в модуле комментариев chat\nВаш логин: " . $login . "\n

Перейдите по ссылке, чтобы активировать ваш аккаунт:\nhttp://" . $_SERVER['HTTP_HOST'] . "/chat/admin/users/activation.php?login=" . $login . "&code=" . $activation . "\nС уважением,\n

Администратор модуля"; //содержание сообщение

mail($email, $subject, $message, "Content-type:text/plane; Charset=utf-8\r\n"); //отправляем сообщение

echo "<hr><h3>Вам на E-mail выслано письмо с cсылкой, для подтверждения регистрации.</h3> <br><b>Внимание! Ссылка действительна 1 час.</b>"; //говорим о отправленном письме пользователю

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

С административным разделом закончили, приступаем к комментариям.

22. Комментарии

Для работы с комментариями используем папку «say», созданную ранее в разделе 3. На данном этапе она пуста и кроме папки «smiles» в которой хранятся заранее подготовленные смайлы в ней ничего нет.

23. Контроллер 3

Для вывода комментариев создаем контроллер 3 «say_controller.php».

Листинг 38. say_controller.php Путь: news/chat/ say_controller.php

<?php

error_reporting(E_ALL);

include_once $_SERVER['DOCUMENT_ROOT'] .'/chat/admin/clean.php';

include $_SERVER['DOCUMENT_ROOT'].'/chat/function/print_comment.php';

include $_SERVER['DOCUMENT_ROOT'].'/chat/function/print_smile_set.php';

$page_id = $_SERVER['PHP_SELF']; //индексируем страницу

/* Если пользователь авторизован вставляем ссылку Добавить комментарий */

if (isset($_SESSION['login'])) {

$userid = $_SESSION['userid'];

echo '<div class="addsay" id=""><a href="?addsay" class="aaddsays">Добавить комментарий</a></div>';

}

else {

$userid = '';

}

/* Устанавливаем условие видимости блока комментариев и кнопок показать и скрыть комментарии */

if (isset($_GET['opensay'])) {

$display_say = 'display:none;';

$display_but = 'display:flow-root;';

}

else {

$display_say = 'display:flow-root;';

$display_but = 'display:none;';

}

/* Применяем условие видимости к кнопкам показать и скрыть комментарии */

echo '<div class="opensay" style="'.$display_but.'"><a href="?" class="aopensays">Показать комментарии</a></div>';

echo '<div class="opensay" style="'.$display_say.'"><a href="?opensay" class="aopensays">Скрыть комментарии</a></div>';

/* Вставляем форму добавить комментарий */

if (isset($_GET['addsay'])){

$pageid = strtok($_SERVER['PHP_SELF'],'?');

include_once $_SERVER['DOCUMENT_ROOT'].'/chat/say/form_addsay.html.php';

}

/* Выводим лист комментариев */

//сортируем в обратном порядке наверху последний комментарий

try {

include $_SERVER['DOCUMENT_ROOT'].'/chat/dsn.php';

      $sql = 'SELECT say.id, say.userid, say.saydate, say.saytext, users.login, users.img FROM say INNER JOIN users ON users.`id` = say.userid WHERE say.page_id = :page_id ORDER BY say.id DESC';

      $s = $dsn->prepare($sql);

$s -> bindValue(':page_id', $page_id);

$s -> execute();

}

catch (PDOException $e) {

echo $e->getMessage();

echo $e->getLine();

exit();

}

foreach ($s as $row) {

$say[] = array(

'id' => $row['id'],

'saytext' => $row['saytext'],

'saydate' => $row['saydate'],

'img' => $row['img'],

'login' => $row['login'],

            'userid' => $row['userid']);

}

include_once $_SERVER['DOCUMENT_ROOT'].'/chat/say/form_say.html.php';

Контроллер определяет условия видимости управляющих комментариями кнопок и форм ввода комментариев, индексирует страницу и формирует массив say[] с комментариями, который будет выводится в форме form_say.html.php после обработки.

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

24. Папка функций
24.1 Печать смайлов

Для вывода набора доступных смайлов написана функция «print_smile_set.php»

Листинг 39. print_smile_set.php Путь: news/function/ print_smile_set.php

<?php

error_reporting(E_ALL);

/* печать блока доступных смайлов в виде кнопок */

function print_smile_set()

{

try {

include $_SERVER['DOCUMENT_ROOT'] . '/chat/dsn.php';

$sql = "SELECT smile, path FROM smiles";

$s = $dsn->query($sql);

$ress = $s->fetchall();

foreach ($ress as $row) :

$smiles_key = $row['smile'];

$smile_path = $row['path']; ?>

<input type="submit" name="smile" title="<?= $smiles_key; ?>" value="<?= $smiles_key; ?>"

style="background:url(<?= $smile_path; ?> );

background-repeat: no-repeat;float:left;height:45px;border:none;

width:50px;font-size:0;" />

<?php

endforeach;

} catch (PDOException $e) {

echo $e->getMessage();

echo $e->getLine();

exit();

}

}

Смайлы сделаны в виде кнопок. В свойстве «background» кнопки идет картинка смайла. Нажатие кнопки вставляет смайл.

Внимание! Стили CSS для смайлов задаются непосредственно в скрипте в строке <input … style=””>

24.2 Печать комментариев

Для печати комментариев служит функция «print_comment.php»

Листинг 40. print_comment.php Путь: news/function/ print_comment.php

<?php

/* печать комментария со смайлами */

function comment_to_smile($comment)

{

try {

include $_SERVER['DOCUMENT_ROOT'] . '/chat/dsn.php';

$sql = "SELECT smile, path FROM smiles";

$s = $dsn->query($sql);

$ress = $s->fetchall();

foreach ($ress as $row) :

$smiles_key = $row['smile'];

$smile_path = $row['path'];

endforeach;

} catch (PDOException $e) {

echo $e->getMessage();

echo $e->getLine();

exit();

}

$smile_path = array_column($ress, 'path'); // Массив с кодами смайлов

$smiles_keys = array_column($ress, 'smile'); // Массив с соответствующими путями к изображениям смайлов

for ($i = 0; $i < count($smile_path); $i++) {

$smile_path[$i] = "<img src='" . $smile_path[$i] . "' alt='' />";//получаем изображение смайла

}

$comment = str_replace($smiles_keys, $smile_path, $comment); //Меняем в комментарии ключи смайлов на пути к смайлам

echo $comment; //печатаем комментарий со смайлами

}

Получаем из таблицы smiles массивы условных обозначений смайлов и путей к их изображениям. В цикле обрабатываем и выводим на печать комментарии со смайлами.

25. Страница сепарации данных

Страница separate_action.php получает данные из формы formaddsay.html.php и обрабатывает их. Введение этой страницы было необходимо для обнуления $_POST на странице обработки комментариев после их отправки. Если этого не сделать, то при размещении этого кода на странице с контроллером 3 при обычном обновлении страницы возникала бы проблема с его обработкой т.к. браузер обычно запоминает данные, которые отправляются с текущей страницы для того, чтобы отправить их снова при обновлении страницы и:

без обнуления переменной POST один и тот же комментарий будет добавляться до бесконечности при каждой перезагрузке страницы.

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

Листинг 41. separate_action.php Путь: news/chat/function/ print_comment.php

<?php

if(session_id() == '') {session_start();}

include_once $_SERVER['DOCUMENT_ROOT'].'./chat/admin/clean.php';

/* включаем/выключаем видимость блока смайлов */

if (isset($_POST['smileblock'])) {

$_SESSION['smileblock']      = 'display:block';

}

if (isset($_POST['smileblock_close'])) {

$_SESSION['smileblock']      = 'display:none';

}

/* Комментарии */

//если была нажата кнопка "Добавить" вставляем форму добавления комментариев,

//если была нажата кнопка "Ответить" вставляем форму ответа на комментарии,

if (isset($_POST['action']) and $_POST['action'] == 'Добавить') {

include_once $_SERVER['DOCUMENT_ROOT'].'/chat/say/makeformaddsay.php';

}

elseif (isset($_POST['action']) and $_POST['action'] == 'Ответить') {

include_once $_SERVER['DOCUMENT_ROOT'].'/chat/say/make_reply.php';

 

}

else {//Проверяем куда пойдет смайл в комментарии или в ответы

include_once $_SERVER['DOCUMENT_ROOT'].'/chat/say/smile_make.php';

}

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

26. Форма для вывода комментариев

Форма form_say.html.php служит для вывода комментариев

Листинг 42. form_say.html.php Путь: news/chat/say/ form_say.html.php

<!DOCTYPE html>

<html>

<head>

<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />

<link rel="stylesheet" type="text/css" href="/chat/style.css" />

</head>

<div class="wrapsayform" style="<?= $display_say ?>">

<?php

include $_SERVER['DOCUMENT_ROOT'] . '/chat/say/list_reply.php';

if (!empty($say)) {

foreach ($say as $saylist) : ?>

<div class="sayform" id="">

<!– Выводим логин и дату –>

<div class="topprintcomment" id="">

<b><?php echo $saylist['login']; ?></b>

<?php $t = $saylist['saydate'];

echo '<span style="float:right;">' . date("d.m.Y", "$t") . '</span>'; ?>

</div>

<!– Выводим аватар и комментарии –>

<div class="printcomment" id="">

<p></p><img src="<?= '/chat/admin/users/' . $saylist['img'] ?>" class="sayavatar">

<?php

$comment = $saylist['saytext'];

comment_to_smile($comment); //печатаем комментарии

$post_id = $saylist['id'];

/* Активация кнопок */

if (userHasRole('admin')) {

$buttonactive = 'submit'; //если админ делаем активными все кнопки

} elseif ($saylist['userid'] == $userid) {

$buttonactive = 'submit'; //делаем активными кнопки для юзера

} else {

$buttonactive = 'hidden'; //отключаем кнопки

} ?></p>

</div><!– END printcomment –>

<!– Выводим блок кнопок –>

<div class="block_button_say" id="">

<div class="wrappersaybutton">

<div class="reply_button" id="">

<div class="postnumber" id=""><?php echo '#' . $saylist['id']; ?> </div>

<a href="?reply=<?php htmlout($post_id); ?>" class="areply">Ответить</a>

</div>

<div class="sayright">

<div class="button_say_edit">

<form name="" method="post" action="/chat/say/sayedit.php" class="logout">

<input type="hidden" name="pageid" id="" value=" <?php echo $pageid; ?>" />

<input type="hidden" name="textedit" value="<?php echo $saylist['id']; ?>">

<input type="hidden" name="saytext" id="" value="<?php echo $saylist['saytext']; ?>" />

<input type="<?= $buttonactive ?>" name="actionedit" value="Редактировать" />

</form>

</div>

<div class="button_say_delete">

<form name="sayform" method="post" action="/chat/say/reset.php" class="logout">

<input type="hidden" name="pageid" id="" value=" <?php echo $pageid; ?>" />

<input type="hidden" name="deleteid" id="" value=" <?php echo $saylist['id']; ?>" />

<input type="<?= $buttonactive ?>" name="delete" id="" value="Удалить" />

</form>

</div>

</div>

</div>

            <!– Форма ответить на комментарий –>

<div class="add_reply" id="">

<?php

if (isset($_SESSION['login'])) {

if (isset($_GET['reply']) and $_GET['reply'] == $post_id) {

include_once $_SERVER['DOCUMENT_ROOT'] . '/chat/say/form_add_reply.html.php';

}

} ?>

</div>

</div><!– END block_button_say –>

</div><!– END sayform –>

<!– Выводим ответы на комментарии и кнопки –>

<?php

include $_SERVER['DOCUMENT_ROOT'] . '/chat/say/print_reply.html.php';

endforeach;

} ?>

</div><!– END wrapsayform –>

</html>

Здесь вставляется, подготовленный в list_reply.php массив ответов на комментарии $reply, затем в цикле foreach – endforeach обрабатываются комментарии, содержащиеся в массиве $say, созданном в контроллере 3, которые выводятся в соответствующих блоках div и происходит активация кнопок для каждого комментария, затем в этом же цикле подключается файл print_reply.html.php, который печатает ответы и выводит кнопки ответов.