Сценарий знакомый: пользователь нажимает кнопку голосования, интерфейс показывает успех, но в админке или в базе результат не меняется. На практике причина обычно не в самом опросе, а в связке AJAX + nonce + кэш. Если опросы на сайте работают через отправку без перезагрузки страницы, любой сбой в этих трех местах ломает запись голоса.
Ниже разберем, как быстро локализовать проблему, что именно исправлять в коде и как проверить, что голос действительно сохраняется, а не только “успешно” отрисовывается на фронтенде.
Как выглядит проблема на практике
Чаще всего пользователь видит один из таких симптомов:
- кнопка голосования отрабатывает, но счетчик не меняется;
- после обновления страницы голос пропадает;
- в консоли браузера есть ошибка
403или400; - сервер возвращает JSON с
success: true, но запись в базе не создается; - голос сохраняется только у части посетителей, например без кэша или только для авторизованных.
Если у вас уже есть защита от повторных голосов, не путайте ее с проблемой сохранения. При защите пользователь обычно получает понятный отказ, а при сбое сохранения система ведет себя так, будто голос принят, но данные не пишутся.
Диагностика: где ломается цепочка
Проверять нужно по порядку: фронтенд, AJAX-запрос, серверный обработчик, запись в хранилище. Это экономит время, потому что “не сохраняется” может означать совсем разные вещи.
1. Проверяем запрос в браузере
Откройте DevTools → Network и отправьте голос еще раз. Важно посмотреть:
- какой URL вызывается;
- какой статус ответа пришел;
- есть ли в запросе
action,nonceи ID опроса; - что возвращает сервер: HTML, JSON или ошибку.
Если вместо JSON приходит HTML страницы логина или страница ошибки, значит запрос перехватывается кэшем, защитой или неверным роутингом.
2. Смотрим логику nonce
Nonce в WordPress не является “паролем”, но он должен совпадать с тем, что сервер ожидает. Типичная ошибка — nonce создается в одном месте, а проверяется другим action-именем. Вторая частая проблема — nonce устаревает, а фронтенд не обновляет его после долгой сессии.
3. Проверяем, не мешает ли кэш
Если страница с опросом отдается из полного кэша, пользователь может видеть устаревшую форму или старый nonce. Для AJAX это особенно неприятно: форма выглядит рабочей, но сервер отклоняет запрос как невалидный.
Если на сайте есть плагин кэширования, сначала проверьте страницу с опросом в режиме без кэша или добавьте исключение для блока/страницы с голосованием.
Пошаговое решение для AJAX-опроса
Ниже пример минимальной схемы: фронтенд отправляет запрос, сервер проверяет nonce и сохраняет голос в post_meta. Это не единственный способ, но он хорошо подходит для диагностики и небольших проектов.
Шаг 1. Подключаем скрипт и передаем данные
<?php
add_action('wp_enqueue_scripts', function () {
wp_enqueue_script(
'poll-vote',
get_stylesheet_directory_uri() . '/js/poll-vote.js',
array('jquery'),
'1.0',
true
);
wp_localize_script('poll-vote', 'PollVoteData', array(
'ajaxUrl' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('poll_vote_action'),
));
});Здесь важно, чтобы poll_vote_action совпадал с проверкой на сервере. Если action-строка отличается хотя бы на один символ, nonce не пройдет валидацию.
Шаг 2. Отправляем голос с фронтенда
jQuery(function ($) {
$('.poll-form').on('submit', function (e) {
e.preventDefault();
var $form = $(this);
var answerId = $form.find('input[name="answer_id"]:checked').val();
var pollId = $form.find('input[name="poll_id"]').val();
$.post(PollVoteData.ajaxUrl, {
action: 'poll_vote',
nonce: PollVoteData.nonce,
poll_id: pollId,
answer_id: answerId
})
.done(function (response) {
if (response && response.success) {
$form.replaceWith(response.data.html);
} else {
alert(response.data && response.data.message ? response.data.message : 'Не удалось сохранить голос');
}
})
.fail(function () {
alert('Ошибка запроса к серверу');
});
});
});Обратите внимание: фронтенд не должен считать успехом просто факт ответа сервера. Проверяйте именно response.success. Иначе можно получить ложноположительный результат, когда сервер вернул ошибку в JSON, а интерфейс все равно показал “спасибо за голос”.
Шаг 3. Обрабатываем запрос на сервере
<?php
add_action('wp_ajax_poll_vote', 'handle_poll_vote');
add_action('wp_ajax_nopriv_poll_vote', 'handle_poll_vote');
function handle_poll_vote() {
check_ajax_referer('poll_vote_action', 'nonce');
$poll_id = isset($_POST['poll_id']) ? absint($_POST['poll_id']) : 0;
$answer_id = isset($_POST['answer_id']) ? absint($_POST['answer_id']) : 0;
if (!$poll_id || !$answer_id) {
wp_send_json_error(array('message' => 'Некорректные данные опроса'));
}
$votes = get_post_meta($poll_id, '_poll_votes', true);
if (!is_array($votes)) {
$votes = array();
}
if (!isset($votes[$answer_id])) {
$votes[$answer_id] = 0;
}
$votes[$answer_id]++;
update_post_meta($poll_id, '_poll_votes', $votes);
ob_start();
?>
<div class="poll-result">Голос принят</div>
<?php
$html = ob_get_clean();
wp_send_json_success(array(
'html' => $html,
));
}Если у вас голос не сохраняется даже с таким кодом, проблема уже не в AJAX как таковом. Тогда проверяйте права на запись, логику хранения и конфликт с другими обработчиками.
Что делать, если виноват кэш
Кэш чаще всего ломает не сам запрос, а страницу, на которой лежит форма. Если nonce встроен в HTML и страница отдается из кэша, токен может устареть раньше, чем пользователь отправит голос.
Рабочие варианты:
- исключить страницу с опросом из full-page cache;
- загружать nonce отдельным AJAX-запросом перед отправкой;
- рендерить форму через серверный фрагмент, который не кэшируется;
- не хранить в кэше персонализированные состояния голосования.
Если опрос встроен в карточку товара WooCommerce, особенно внимательно проверьте кэш на страницах каталога и single product. Там часто кэшируется весь шаблон, включая форму голосования.
Сравнение подходов: плагин, код, гибрид
| Подход | Когда подходит | Минус |
|---|---|---|
| Плагин с готовой логикой | Нужен быстрый запуск без доработок | Сложнее отлаживать nonce и кэш, если плагин не дает прозрачных логов |
| Свой код | Нужен контроль над записью и интеграциями | Нужно самому следить за безопасностью и совместимостью |
| Гибрид: плагин + точечные правки | Есть рабочий плагин, но ломается сохранение на кэше или в теме | Можно унаследовать чужие ограничения и костыли |
Если вы уже используете плагин опросов, сначала проверьте, есть ли у него фильтры или действия для AJAX-обработки. Полезно не переписывать все с нуля, а только исправить место, где запрос отклоняется.
Проверка результата после внедрения
После правок не ограничивайтесь визуальной проверкой. Нужно убедиться, что голос реально записался и не исчезает после обновления страницы.
- отправьте голос в приватном окне браузера;
- проверьте ответ в Network: должен быть JSON с
success: true; - обновите страницу и убедитесь, что результат остался;
- посмотрите метаданные записи в базе или через админку, если плагин это показывает;
- повторите тест с включенным кэшем и без него.
Если голос сохраняется только без кэша, значит исправление частичное. В таком случае нужно либо исключать страницу, либо переводить форму на отдельный динамический запрос.
Частые ошибки и как их исправить
Nonce создается и проверяется под разными именами
Например, в JavaScript отправляется poll_nonce, а на сервере проверяется nonce. WordPress в этом случае вернет отказ, и запрос не пройдет.
Фронтенд считает успехом любой ответ сервера
Если не проверять response.success, интерфейс покажет успех даже при ошибке валидации или записи.
Кэш отдает старую форму
Это особенно заметно на страницах с долгим временем жизни кэша. Пользователь отправляет голос с устаревшим nonce и получает отказ.
Не проверяются входные данные
Если не использовать absint() и не валидировать ID опроса и ответа, можно записать мусорные значения или получить предупреждения PHP.
Нет защиты от повторной отправки
Даже если задача этой статьи — сохранение голоса, не забывайте про повторные клики и двойную отправку формы. Минимум — блокируйте кнопку на время запроса. Лучше — храните факт голосования отдельно и проверяйте его перед записью.
Практические советы по безопасности и производительности
Если опросов много и они активно голосуются, не храните все в одном перегруженном массиве без необходимости. Для небольшого сайта это допустимо, но на более нагруженных проектах лучше заранее продумать структуру хранения. Иначе чтение и обновление одного post_meta станет узким местом.
Из безопасных практик:
- всегда проверяйте nonce через
check_ajax_referer(); - валидируйте все входящие ID через
absint(); - не доверяйте данным из
$_POSTбез очистки; - логируйте ошибки на этапе отладки, а не показывайте их пользователю;
- исключайте страницу с опросом из агрессивного кэша, если форма зависит от nonce.
Если на сайте используется плагин оптимизации вроде Clearfy Pro, проверьте, не включены ли настройки, которые объединяют или откладывают скрипты так, что AJAX-форма инициализируется с опозданием. В таких случаях проблема выглядит как “не сохраняется голос”, хотя на деле скрипт просто не успевает повесить обработчик.
Когда нужен не только опрос, но и квиз с логикой переходов, иногда проще вынести сценарий в специализированный инструмент, чем поддерживать самописную связку. Но для точечной задачи с сохранением голосов обычно достаточно аккуратно починить AJAX, nonce и кэш, не меняя всю архитектуру.