Формы и AJAX на витрине
На странице отзывов нужна форма, чтобы покупатель мог оставить свой отзыв. Отправленные данные контроллер витрины разбирает в методе postProcess() — движок вызывает его раньше initContent(). Для запросов из JavaScript у контроллера есть отдельные методы, которые отвечают JSON.
Форма в шаблоне
Добавьте форму в блок page_content шаблона страницы:
{* modules/acmehello/views/templates/front/reviews.tpl *}
<form method="post" action="{$formUrl}">
<input type="hidden" name="token" value="{$formToken}">
<label>{l s='Имя' d='Modules.Acmehello.Shop'}
<input type="text" name="author" required>
</label>
<label>{l s='Оценка' d='Modules.Acmehello.Shop'}
<input type="number" name="rating" min="1" max="5" value="5">
</label>
<label>{l s='Отзыв' d='Modules.Acmehello.Shop'}
<textarea name="content"></textarea>
</label>
<button type="submit" name="submitReview" class="btn btn-primary">
{l s='Отправить' d='Modules.Acmehello.Shop'}
</button>
</form>Переменные $formUrl и $formToken передаёт контроллер. Добавьте их в assign() метода initContent():
// modules/acmehello/controllers/front/reviews.php
'formUrl' => $this->context->link->getModuleLink('acmehello', 'reviews', ['id_product' => $productId]),
'formToken' => Tools::getToken(false),Tools::getToken(false) возвращает токен — строку, привязанную к сеансу покупателя. По ней контроллер поймёт, что форму отправили со страницы магазина, а не с чужого сайта.
Обработка отправки
// modules/acmehello/controllers/front/reviews.php
public function postProcess(): void
{
if (!Tools::isSubmit('submitReview')) {
return;
}
if (!$this->isTokenValid()) {
$this->errors[] = $this->trans('Форма устарела, отправьте её ещё раз.', [], 'Modules.Acmehello.Shop');
return;
}
$productId = (int) Tools::getValue('id_product');
$author = trim((string) Tools::getValue('author'));
$rating = (int) Tools::getValue('rating');
$content = trim((string) Tools::getValue('content'));
if ($author === '' || !Validate::isGenericName($author)) {
$this->errors[] = $this->trans('Укажите имя.', [], 'Modules.Acmehello.Shop');
}
if ($rating < 1 || $rating > 5) {
$this->errors[] = $this->trans('Оценка — от 1 до 5.', [], 'Modules.Acmehello.Shop');
}
if (!Validate::isMessage($content)) {
$this->errors[] = $this->trans('В тексте есть недопустимые символы.', [], 'Modules.Acmehello.Shop');
}
if ($this->errors) {
return;
}
$this->module->addReview($productId, $author, $rating, $content);
$this->success[] = $this->trans('Спасибо за отзыв!', [], 'Modules.Acmehello.Shop');
$this->redirectWithNotifications(
$this->context->link->getModuleLink('acmehello', 'reviews', ['id_product' => $productId])
);
}Откройте страницу отзывов, заполните форму и отправьте. Страница перезагрузится, вверху появится зелёное сообщение «Спасибо за отзыв!», а в списке — новый отзыв. Если имя пустое, покажется красная ошибка и отзыв не сохранится.
Что здесь происходит
| Вызов | Зачем |
|---|---|
Tools::isSubmit('submitReview') | Проверяет, что форму отправили |
isTokenValid() | Сравнивает поле token с токеном сеанса |
$this->errors[] | Копит ошибки; тема покажет их над содержимым |
$this->success[] | Сообщение об успехе |
redirectWithNotifications() | Перенаправляет и переносит сообщения на следующую страницу |
Перенаправление после успешной отправки нужно, чтобы обновление страницы не отправило форму повторно.
Запрос AJAX
Когда в запросе есть параметр ajax=1, движок вместо initContent() вызывает метод displayAjax плюс значение параметра action:
// modules/acmehello/controllers/front/reviews.php
public function displayAjaxLatest(): void
{
$reviews = $this->module->getReviews((int) Tools::getValue('id_product'));
header('Content-Type: application/json');
$this->ajaxRender(json_encode(['reviews' => $reviews]));
}Откройте /module/acmehello/reviews?id_product=1&ajax=1&action=latest: браузер покажет JSON со списком отзывов, без шапки и подвала.
Вызов из скрипта
Адрес передают в JavaScript через Media::addJsDef() в главном классе модуля:
// modules/acmehello/acmehello.php, метод hookActionFrontControllerSetMedia
Media::addJsDef([
'acmeHelloReviewsUrl' => $this->context->link->getModuleLink('acmehello', 'reviews', [
'id_product' => (int) Tools::getValue('id_product'),
'ajax' => 1,
'action' => 'latest',
]),
]);// modules/acmehello/views/js/front.js
fetch(acmeHelloReviewsUrl)
.then((response) => response.json())
.then((data) => {
console.log(data.reviews.length);
});На странице товара в консоли браузера появится число отзывов.
Частая ошибка
Контроллер принимает форму и не проверяет токен. Тогда чужой сайт может отправить её от имени вошедшего покупателя — это подделка запроса (CSRF). Добавляйте в форму скрытое поле token и вызывайте isTokenValid() в начале postProcess(). То же касается запросов AJAX, которые что-то изменяют.