Доброго часу, шановний відвідувач... давно не постилось на форумі щось ексклюзивне, тому поділюсь з недавнього цікавого та без особливих заморочок у розробці алгоритму, а саме:
архітектура логіки коментарів до статей з можливістю відповіді. Отже, розписую у послідовності як і що розробляв сам:
крок 0: проектування DB, створюємо таблицю з основними полями для коментарів, оскільки логіки, як і у відгуках, багатомовності, не міркую актуальною, тому нормалізацію по мовному ідентифікатору відкидую, і залишаємо ось такі поля:
крок 1: фронт (адмінку залишу на десерт, якщо отримаю фідбек по цікавості теми, то розпишу, як реалізував логіку по виводу коментарів у адмінці з маркуванням "адмін" у самих коментарях, щоб виділялись від решти відповідей), а саме, у контроллері catalog/controller/information/information.php перед
також, у самому контроллері, я нижче додав метод (приватний), котрий формує асоціативний массив з одномірного, котрий витягнули з бази по ідентифікатору статті, перед методом
та ще один метод, котрий додає коментар безпосередньо зі статті (додаємо одразу після методу, котрий описаний вище):
крок 2: модель... тут нам потрібно додати 3-ри методи: getComents, getFio та addCommentToInfo, якщо з першим зрозуміло, то 2-й витягуватиме ім'я користувача, котрий залишив коментар, оскільки я реалізував систему тільки для авторизованих покупців, тому в мене в таблицю заноситься ідентифікатор користувача, котрий залишив коментар... а 3-й метод додає до таблиці сам коментар... отже, додаємо у модель catalog/model/catalog/information.php 3-ри методи:
крок 3: виводимо усі дані уже безпосередньо під статею (не зорієнтуюсь по верстці, як то під bootstrap буде, та думаю логіку вловиш та імплементуш під свій вид) у файлі: catalog/view/theme/default/template/information/information.tpl, знаходимо ось такий рядок:
а в самому низу, перед <?php echo $footer; ?> додаємо ось такі рядочки коду на javaScript:

enjoy... bro
p.s. для 0-го (першого коментарію) форму не описав, та у мене то просто форма для коментарію з 0-м ID, по типу ось такого виду:
архітектура логіки коментарів до статей з можливістю відповіді. Отже, розписую у послідовності як і що розробляв сам:
крок 0: проектування DB, створюємо таблицю з основними полями для коментарів, оскільки логіки, як і у відгуках, багатомовності, не міркую актуальною, тому нормалізацію по мовному ідентифікатору відкидую, і залишаємо ось такі поля:
--
-- Структура таблиці `oc_information_comments`
--
CREATE TABLE `oc_information_comments` (
`comment_id` int(11) NOT NULL,
`customer_id` int(11) NOT NULL,
`information_id` int(11) NOT NULL,
`reply` int(11) NOT NULL DEFAULT 0,
`is_admin` tinyint(1) NOT NULL DEFAULT 0,
`data_add` datetime NOT NULL DEFAULT current_timestamp(),
`text` text NOT NULL,
`status` tinyint(1) NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
--
-- Індекси таблиці `oc_information_comments`
--
ALTER TABLE `oc_information_comments`
ADD PRIMARY KEY (`comment_id`);
--
-- AUTO_INCREMENT для таблиці `oc_information_comments`
--
ALTER TABLE `oc_information_comments`
MODIFY `comment_id` int(11) NOT NULL AUTO_INCREMENT;
COMMIT;
крок 1: фронт (адмінку залишу на десерт, якщо отримаю фідбек по цікавості теми, то розпишу, як реалізував логіку по виводу коментарів у адмінці з маркуванням "адмін" у самих коментарях, щоб виділялись від решти відповідей), а саме, у контроллері catalog/controller/information/information.php перед
$data['continue'] = $this->url->link('common/home');додаємо ось такий код (за конвенцію беремо макс. рівень вкладеності 3-й рівень відповідей на коментарі):$data['send'] = $this->url->link('information/information/addComment');
$data['cname'] = '';
if ($this->customer->isLogged()) {
$data['cname'] =$this->model_catalog_information->getFio($this->customer->getId());
}
$data['current_url'] = $this->url->link('information/information', 'information_id=' . $information_id);
$data['info_id'] = (int)$information_id;
$coments = $this->model_catalog_information->getComents($information_id);
if ($coments) {
$comentaries = $this->resortComments($coments);
$main_data = array();
$children_data = array();
$onyk_data = array();
foreach ($comentaries as $comentary) {
if ($comentary['child']) {
foreach ($comentary['child'] as $child_comentary) {
if ($child_comentary['child']) {
foreach ($child_comentary['child'] as $onyk_comentary) {
$onyk_data[$onyk_comentary['comment_id']] = array(
'name' => $this->model_catalog_information->getFio($onyk_comentary['customer_id']),
'is_admin' => (int)$onyk_comentary['is_admin'],
'timer' => $onyk_comentary['data_add'],
'text' => html_entity_decode($onyk_comentary['text'], ENT_QUOTES, 'UTF-8'),
'child' => false,
'total' => 0,
);
}
}
$children_data[$child_comentary['comment_id']] = array(
'name' => $this->model_catalog_information->getFio($child_comentary['customer_id']),
'timer' => $child_comentary['data_add'],
'is_admin' => (int)$child_comentary['is_admin'],
'text' => html_entity_decode($child_comentary['text'], ENT_QUOTES, 'UTF-8'),
'child' => $onyk_data,
'total' => ($child_comentary['child'] ? count($child_comentary['child']) : 0),
);
}
}
$data['coments'][$comentary['comment_id']] = array(
'name' => $this->model_catalog_information->getFio($comentary['customer_id']),
'timer' => $comentary['data_add'],
'is_admin' => (int)$comentary['is_admin'],
'text' => html_entity_decode($comentary['text'], ENT_QUOTES, 'UTF-8'),
'child' => $children_data,
'total' => ($comentary['child'] ? count($comentary['child']) : 0),
);
}
}також, у самому контроллері, я нижче додав метод (приватний), котрий формує асоціативний массив з одномірного, котрий витягнули з бази по ідентифікатору статті, перед методом
public function agree():private function resortComments($coments = array(), $parent = 0) {
$items = array();
if ($coments) {
foreach ($coments as $item) {
if ($item['reply'] == $parent) {
$item['child'] = isset($item['child']) ? $item['child'] : $this->resortComments($coments, $item['comment_id']);
$item['total'] = ($item['child'] ? count($item['child']) : 0);
$items[$item['comment_id']] = $item;
}
}
}
return $items;
}та ще один метод, котрий додає коментар безпосередньо зі статті (додаємо одразу після методу, котрий описаний вище):
public function addComment() {
$json = array();
if ($this->request->server['REQUEST_METHOD'] == 'POST') {
$data = array(
'text' => (string)$this->request->post['text'],
'reply' => (int)$this->request->post['reply'],
'info_id' => (int)$this->request->post['info_id']
);
$this->load->model('catalog/information');
$json['success'] = $this->model_catalog_information->addCommentToInfo($data);
}
$this->response->addHeader('Content-Type: application/json');
$this->response->setOutput(json_encode($json));
}крок 2: модель... тут нам потрібно додати 3-ри методи: getComents, getFio та addCommentToInfo, якщо з першим зрозуміло, то 2-й витягуватиме ім'я користувача, котрий залишив коментар, оскільки я реалізував систему тільки для авторизованих покупців, тому в мене в таблицю заноситься ідентифікатор користувача, котрий залишив коментар... а 3-й метод додає до таблиці сам коментар... отже, додаємо у модель catalog/model/catalog/information.php 3-ри методи:
public function getFio($customer_id) {
$query = $this->db->query("SELECT firstname AS name FROM " . DB_PREFIX . "customer WHERE customer_id = '" . (int)$customer_id . "'");
return $query->row['name'];
}
public function getComents($information_id = 0) {
$data = array();
if ($information_id) {
$sql = "SELECT * FROM " . DB_PREFIX . "information_comments WHERE information_id = '" . (int)$information_id . "' ORDER BY data_add DESC";
$query = $this->db->query($sql);
if ($query->num_rows) {
$data = $query->rows;
}
}
return $data;
}
public function addCommentToInfo($data = array()) {
if ($data) {
if ($this->customer->isLogged()) {
$customer_id = $this->customer->getId();
} else {
return false;
}
$sql = "INSERT INTO " . DB_PREFIX . "nformation_comments (`customer_id`, `information_id`, `reply`, `is_admin`, `text`, `status`) VALUES (" . (int)$customer_id . "," . $data['info_id'] . "," . $data['reply'] . ",'0','" . $this->db->escape($data['text']) . "','0')";
$this->db->query($sql);
}
return true;
}крок 3: виводимо усі дані уже безпосередньо під статею (не зорієнтуюсь по верстці, як то під bootstrap буде, та думаю логіку вловиш та імплементуш під свій вид) у файлі: catalog/view/theme/default/template/information/information.tpl, знаходимо ось такий рядок:
<?php echo $description; ?><?php echo $content_bottom; ?></div>та формуємо ось таку структуру:<?php echo $description; ?>
<!-- SOF comments block -->
<?php if ($coments) { ?>
<div class="comments-page-instruction__items">
<?php foreach ($coments as $comment_id => $coment) { ?>
<div class="item-comments-page-instruction <?php if ($coment['is_admin']) { ?>admin-coment<?php } ?>">
<div class="item-comments-page-instruction__top">
<div class="item-comments-page-instruction__title"><?php echo $coment['name']; ?></div>
<div class="item-comments-page-instruction__time"><?php echo $coment['timer']; ?></div>
</div>
<div class="item-comments-page-instruction__text">
<?php echo $coment['text']; ?>
</div>
<div class="item-comments-page-instruction__bottom">
<button onclick="toggleComment(this);" class="form-comments-page-instruction__button">
<?php echo $text_ansver; ?>
</button>
<form action="<?php echo $send; ?>" class="comments-page-instruction__form" method="post" enctype="multipart/form-data" id="comment-form-<?php echo $comment_id; ?>">
<label class="label form-comments-page-instruction__name" for="name-<?php echo $comment_id; ?>">
<input autocomplete="off" type="text" class="input" value="<?php echo $cname; ?>" readonly="true" id="name-<?php echo $comment_id; ?>">
<span class="form__span"><?php echo $text_name; ?>*</span>
</label>
<label for="text-<?php echo $comment_id; ?>" class="label leave-feedback-tabs-body__response">
<textarea autocomplete="off" name="customtext" placeholder="<?php echo $text_comment; ?>" class="input leave-feedback-tabs-body__response-textarea" id="text-<?php echo $comment_id; ?>" required></textarea>
<span class="form__span"><?php echo $text_comment; ?></span>
</label>
<input type="hidden" name="reply" value="<?php echo $comment_id; ?>">
<div class="form-comments-page-instruction__buttons">
<button class="form-comments-page-instruction__button">
<?php echo $text_cancel; ?>
</button>
<button class="form-comments-page-instruction__button" onclick="validateCommentForm(<?php echo $comment_id; ?>);">
<?php echo $text_send; ?>
</button>
</div>
</form>
</div>
<?php if ($coment['child']) { ?>
<div data-spollers class="item-comments-page-instruction__spoilers">
<button data-spoller class="item-comments-page-instruction__spoiler">
<span><?php echo $coment['total']; ?></span>
<span class="item-comments-page-instruction__spoiler-arrow"></span>
</button>
<div class="item-comments-page-instruction__spoiler-body">
<?php foreach ($coment['child'] as $child_comment_id => $child_coment) { ?>
<div class="item-comments-page-instruction <?php if ($child_coment['is_admin']) { ?>admin-coment<?php } ?>">
<div class="item-comments-page-instruction__top">
<div class="item-comments-page-instruction__title"><?php echo $child_coment['name']; ?></div>
<div class="item-comments-page-instruction__time"><?php echo $child_coment['timer']; ?></div>
</div>
<div class="item-comments-page-instruction__text">
<?php echo $child_coment['text']; ?>
</div>
<div class="item-comments-page-instruction__bottom">
<button onclick="toggleComment(this);" class="form-comments-page-instruction__button">
<?php echo $text_ansver; ?>
</button>
<form action="<?php echo $send; ?>" class="comments-page-instruction__form" method="post" enctype="multipart/form-data" id="comment-form-<?php echo $child_comment_id; ?>">
<label class="label form-comments-page-instruction__name" for="name-<?php echo $child_comment_id; ?>">
<input type="text" name="customname" class="input" value="<?php echo $cname; ?>" readonly="true" id="name-<?php echo $child_comment_id; ?>">
<span class="form__span"><?php echo $text_name; ?>*</span>
</label>
<label for="text-<?php echo $child_comment_id; ?>" class="label leave-feedback-tabs-body__response">
<textarea autocomplete="off" name="customtext" placeholder="<?php echo $text_comment; ?>" class="input leave-feedback-tabs-body__response-textarea" id="text-<?php echo $child_comment_id; ?>" required></textarea>
<span class="form__span"><?php echo $text_comment; ?></span>
</label>
<input type="hidden" name="reply" value="<?php echo $child_comment_id; ?>">
<div class="form-comments-page-instruction__buttons">
<button class="form-comments-page-instruction__button">
<?php echo $text_cancel; ?>
</button>
<button class="form-comments-page-instruction__button" onclick="validateCommentForm(<?php echo $child_comment_id; ?>);">
<?php echo $text_send; ?>
</button>
</div>
</form>
</div>
<?php if ($child_coment['child']) { ?>
<div data-spollers class="item-comments-page-instruction__spoilers">
<button data-spoller class="item-comments-page-instruction__spoiler">
<span><?php echo $child_coment['total']; ?></span>
<span class="item-comments-page-instruction__spoiler-arrow"></span>
</button>
<div class="item-comments-page-instruction__spoiler-body">
<?php foreach ($child_coment['child'] as $onyk_comment_id => $onyk_coment) { ?>
<div class="item-comments-page-instruction <?php if ($onyk_coment['is_admin']) { ?>admin-coment<?php } ?>">
<div class="item-comments-page-instruction__top">
<div class="item-comments-page-instruction__title"><?php echo $onyk_coment['name']; ?></div>
<div class="item-comments-page-instruction__time"><?php echo $onyk_coment['timer']; ?></div>
</div>
<div class="item-comments-page-instruction__text">
<?php echo $onyk_coment['text']; ?>
</div>
</div>
<?php } ?>
</div>
</div>
<?php } ?>
</div>
<?php } ?>
</div>
</div>
<?php } ?>
</div>
<?php } ?>
</div>
<?php } ?>
<!-- EOF comments block -->
<?php echo $content_bottom; ?></div>
а в самому низу, перед <?php echo $footer; ?> додаємо ось такі рядочки коду на javaScript:
<script>
function validateCommentForm(comment_id) {
const form = document.getElementById("comment-form-" + comment_id);
form.addEventListener("submit", (event) => {
event.preventDefault();
const comment_url = form.getAttribute("action");
let err_comment = document.getElementById("err-comment");
if (err_comment) {
err_comment.remove();
}
let text = document.getElementById("text-" + comment_id);
if (text.classList.contains('_form-error')) {
text.classList.remove('_form-error');
text.parentElement.classList.remove('_form-error');
}
if (!text.value.trim() || text.value.length < 4 || text.value.length > 1024) {
text.classList.add('_form-error');
text.parentElement.classList.add('_form-error');
text.parentElement.insertAdjacentHTML('beforeend', '<div id="err-comment" class="form__error">' + 'Коментар повинен містити хоча б одне слово!' + '</div>');
return false;
}
const xhttp = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP");
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
let response = JSON.parse(this.responseText);
if (response['success']) {
window.location.href = "<?php echo $current_url; ?>";
}
}
};
xhttp.open("POST", comment_url, true);
xhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
xhttp.send("text=" + text.value + "&reply=" + form.reply.value + "&info_id=<?php echo $info_id?>");
});
}
</script>

enjoy... bro
p.s. для 0-го (першого коментарію) форму не описав, та у мене то просто форма для коментарію з 0-м ID, по типу ось такого виду:
<form action="<?php echo $send; ?>" class="comments-page-instruction__form" method="post" enctype="multipart/form-data" id="comment-form-0">Вкладення: 1693076650587.png
<label class="label form-comments-page-instruction__name" for="name-0">
<input type="text" name="customname" class="input" value="<?php echo $cname; ?>" readonly="true" id="name-0">
<span class="form__span"><?php echo $text_name; ?>*</span>
</label>
<label for="text-0" class="label leave-feedback-tabs-body__response">
<textarea autocomplete="off" name="customtext" placeholder="<?php echo $text_comment; ?>" class="input leave-feedback-tabs-body__response-textarea" id="text-0" required></textarea>
<span class="form__span"><?php echo $text_comment; ?></span>
</label>
<input type="hidden" name="reply" value="0">
<div class="form-comments-page-instruction__buttons">
<button class="form-comments-page-instruction__button">
<?php echo $text_cancel; ?>
</button>
<button class="form-comments-page-instruction__button" onclick="validateCommentForm('0');">
<?php echo $text_send; ?>
</button>
</div>
</form>