Encodera

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.

Оглавление

Формы и 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, которые что-то изменяют.

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.