CommerCentr
EN
Sign in

Коментарії до статей (з відповідями) для opencart

#1
Доброго часу, шановний відвідувач... давно не постилось на форумі щось ексклюзивне, тому поділюсь з недавнього цікавого та без особливих заморочок у розробці алгоритму, а саме:
архітектура логіки коментарів до статей з можливістю відповіді. Отже, розписую у послідовності як і що розробляв сам:
крок 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>


1693076650587.png

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">
<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>
Вкладення: 1693076650587.png
#2
А баба яга проти )))

1. Робити окремий контролер, це дасть можливість зробити окремий функціонал. ООП!!!!

2. нативний js - для чого?

3. Таблиця.
поле replay, краще перейменувати на parent_id або reply_id вказавши, що це якась залежність
4. Чому статус -
tinyint(4) NOT

6. Де валідація вхідних даних?
Baco
$data = array( 'text' => (string)$this->request->post['text'],
'reply' => (int)$this->request->post['reply'],
'info_id' => (int)$this->request->post['info_id'] );

#3
Ну... я так і очікував, що внутрішній перфекціоність дасть знати про себе)) насправді, @SlaSoft правий, по принципу ООП, кожну виокремлену сутність, краще робить окремим класом, щодо перейменовування ключів\полей у таблиці, то це вже на смак, хоча так, як Ти описав, буде інтуітивно зрозуміліше при підтримці (як у тому тезисі про "маніяка параноїка, котрий знає, де ти живаеш"), щодо tinyint(4), то погоджуюсь, хватить і 1, то я щось проґавив, дякую... а от щодо нативного рішення, то це моя особиста примха, я відмовляюсь від використання сторонніх бібліотек, пишучи автономні рішення (по типу, як би аналітика, чати, маркетинг зав'язані були на jQuery, до яких би то наслідків призвело, але це особисте, кожен ладен кодити на свій смак, я надав тільки "вудочку" і показав, як я "ловлю" :cool: ), то ж загалом, ось так і народжуються хороші рішення, у конструктивному диспуті.

Anyone can read. To post and vote, sign in.