WordPress, будучи одной из самых популярных CMS в мире, снабжен подробной документацией, а точнее, даже двумя. В связи с чем ни в коем случае не стоит воспринимать этот текст как описание неких “best practices” и уж точно никто не заставляет слепо следовать описанному. Статья — просто быстрый ответ на вопрос «как?!» (следующий абзац) и подробное описание всего, что нужно знать чтобы заставить WordPress отвечать на AJAX-запросы (вся остальная статья).
Кратко
Традиционно для AJAX-запросов нужно две вещи: скрипт на сервере (бекенд), который будет отвечать на запросы, и скрипт на клиенте (фронтенд), который будет эти запросы делать. WordPress позволяет делегировать функции на обращение к специальному URL, по которому находится обработчик запросов.
Итак, работает это, «WordPress-way», вот так:
- На бекенде с помощью функции
admin_url
получаем ссылку на обработчик AJAX-запросов и передаем ее во фронтенд одним из способов. Именно к этой ссылке мы будем делать наши запросы. - На бекенде регистрируется хук с функцией для обработки некоего экшена. Назовем этот экшен, например, get_posts.
- Фронт-енд делает запросы к URL-у из пункта 1, передавая имя экшена. Например,
?action=get_posts
.
На бек-енде, если на экшен зарегистрирован хук, выполняется заданная нами функция.
Вот так вот просто. Теперь подробнее.
Подробно
AJAX-обработчик на бекенде
Некоторые люди делают AJAX-обработчик вручную, путем инклюда файла wp_load. Это считается очень и очень плохой практикой по целой куче причин. У WordPress есть свой, готовый обработчик. Будьте паиньками и пользуйтесь им.
После того, как фронтенд «узнал», куда ему слать запросы, нужно добавить на бекенде хук для обработки этих запросов. Обязательный параметр в этих запросах —
action
: этот параметр определяет, что именно мы "хотим" от бекенда.Для того, чтобы создать новый метод AJAX-обработчика, нужно повесить два хука:
wp_ajax_<имя экшена>
и wp_ajax_nopriv_<имя экшена>
. Например, вот так:add_action('wp_ajax_get_posts' , 'get_posts_callback');
add_action('wp_ajax_nopriv_get_posts', 'get_posts_callback');
Префикс
wp_ajax_
вначале имени хука даст WordPress понять, что мы пытаемся создать обработчик AJAX-запроса. Префикс wp_ajax_nopriv
позволяет зарегистрировать хук для незалогиненных пользователей. Таким образом можно зарегистрировать разные обработчики для залогиненных и незалогиненных юзеров, что может быть удобно. При этом, если вам нужно, чтобы ajax-запрос выполнялся и для тех, и для других, вам придётся повесить одну функцию на оба хука.Эти хуки — самые обычные, такие же, как все остальные WP-хуки. В результате регистрации таких хуков при обращении к УРЛ-у
wp-admin/admin-ajax.php?action=get_posts
должна выполниться функция get_posts_callback
.add_action( 'wp_ajax_do_something', 'get_posts_callback' ); // For logged in users
add_action( 'wp_ajax_nopriv_do_something', 'get_posts_callback' ); // For anonymous users
function do_something_callback(){
echo(json_encode( array('status'=>'ok','request_vars'=>$_REQUEST) ));
wp_die();
}
В результате выполнения этого кода по ссылке вида
wp-admin/admin-ajax.php?action=do_something
должен выводиться кусок JSON-а.Некоторые особенности на бекенде
Наверное, имеет смысл заметить, что хуки в WordPress вешаются каждый раз. Т. е. обработчик нужно регистрировать каждый раз, желательно в файле, который включается каждый раз (в случае с темой это файл functions.php) Если попытаться зарегистрировать AJAX-хук, например, в файлах page.php или index.php темы, хук не будет работать, потому что при обращении к обработчику эти файлы, разумеется, не будут выполняться.
Рекомендуется все функции, повешенные на экшены AJAX-обработчика, заканчивать вызовом
wp_die
или функции wp_send_json_success
и аналогичных. Или простой die
, на худой конец, вызвать.В случае ошибки обработчик запросов возвращает код 0 или -1 в зависимости от причины ошибки. В частности, в случае, если такого хука не существует (т. е. он не был зарегистрирован по какой-то причине) — возвращается 0.
Функция
is_admin
, возвращающая true, если пользователь находится в админпанели сайта, будучи вызванной из AJAX-обработчика, всегда возвращает true.Существует функция
check_ajax_referer
, которая проверяет реферера (проверяет, откуда произошел запрос) и прерывает выполнение, если реферер какой-то «не такой». Эта функция умеет также проверятьnonce. Подробнее можно почитать в соответствующей статье кодекса.
Передача ссылки на фронтенд
Адрес обработчика запросов — что-то типа
wp-admin/admin-ajax.php
. Фишка в том, что наш фронтенд "не знает" адреса этой ссылки. Поэтому, если мы хотим, чтобы наша тема или плагин была максимально универсальной и портативной — нам нужно получить в WordPress и передать на фронтенд актуальный адрес этой ссылки:$ajax_url = admin_url('admin-ajax.php');
На фронтенд её можно передать самыми разными способами. Зачастую эта ссылка — не единственное, что нужно передавать на фронтенд, поэтому я предпочитаю вставлять её c помощью соответствующего хука в wp_head в теге
<script>
:// где-то в functions.php
function js_variables(){
$variables = array (
'ajax_url' => admin_url('admin-ajax.php'),
'is_mobile' => wp_is_mobile()
// Тут обычно какие-то другие переменные
);
echo(
'<script type="text/javascript">window.wp_data = ',
json_encode($variables),
';</script>'
);
}
add_action('wp_head','js_variables');
Этот хук будет выводить тег
<script>
с регистрацией глобальных JS-переменных со ссылкой на обработчик в в секции <head>
темы (разумеется, для этого нужно вызвать в ней функцию wp_head
, что в любом случае рекомендуется делать для любой темы)Тэг будет вставляться сразу после всех подключенных ассетов (скриптов и таблиц стилей, зарегистрированных через систему ассетов WordPress)
Передать ссылку во фронтенд можно также с помощью функции
localize_script
. Я нахожу такой подход несколько муторным: де-факто, мы отправляем на фронтенд не наш джаваскрипт из папки assets темы или с какого-нибудь CDN, но его модифицированную версию, при этом не оставляя конечному пользователю выбора (который у него есть, например, в случае с плагинами, склеивающими все скрипты в один и заключается в том, использовать ему эти плагины или нет). Если что — подробнее об подходе с localize_script
— в кодексе вордпресса, в статье AJAX In Plugins.И, разумеется, можно НЕ передавать эту ссылку на фронтенд, а просто её или её часть захардкодить прямо в в JS-файл. Но никто не гарантирует, что в следующей версии WordPress или просто на каком-то другом конкретном энвайрменте URI обработчика будет именно 'wp-admin/admin-ajax.php'. Для универсальности и совместимости рекомендуется всегда использовать функцию admin_url для получения актуальной ссылки на обработчик и передавать её во фронтенд вручную.
Кстати, на всех страницах админки эта ссылка уже проброшена в глобальную переменную ajaxurl в JS. Некоторые плагины вывешивают эту ссылку и для страниц сайта, но на это не стОит рассчитывать (мы ведь не хотим лишних зависимостей для нашего плагина/темы, правда?)
На фронтенде
Теперь мы можем достаточно легко делать веб-запросы к бекенду с нашего фронтенда. Например, если ссылку мы вывесили в переменную wp_data.ajax_url и подключен jQuery — выглядеть это будет примерно вот так:
jQuery(function($){
$.ajax({
type: "GET",
url: window.wp_data.ajax_url,
data: {
action : 'do_something'
},
success: function (response) {
console.log('AJAX response : ',response);
}
});
});
Если всё сделано правильно — при запуске выполнится веб-запрос к бекенду, который ответит куском JSON-а, который потом будет получен фронтендом и выведен в консоль.
Любителям ООП
Если вы пытаетесь построить свою собственную архитектуру темы, с автозагрузчиком и ООП, можно определить абстрактный класс вот такого типа:
abstract class AJAX_Handler {
function __construct($action_name) {
$this->init_hooks($action_name);
}
public function init_hooks($action_name) {
add_action('wp_ajax_'.$action_name , array($this,'callback'));
add_action('wp_ajax_nopriv_'.$action_name, array($this,'callback_nopriv'));
}
public function callback_nopriv() {
$this->callback();
}
abstract public function callback();
}
Теперь достаточно создать для каждого экшена своего наследника этого класса, в котором определить метод callback, после чего создать новый объект класса-наследника, передав в конструктор имя экшена:
class Something extends AJAX_Handler {
function callback() {
wp_send_json_success(array('test'=>'Works!'));
}
}
new Something('do_something');
На хук wp_ajax_nopriv_XXX вешается метод
callback_nopriv
, который в родительском классе просто вызывает метод callback
, но в наследнике её, разумеется, можно было переопределить. В результате этого при обращении к урл вида /wp-admin/admin-ajax.php?action=do_something
получим ответ вида {"success":true,"data":{"test":"Works!"}}
. CORS и возможная беда с протоколом (HTTP/HTTPS)
По соображениям безопасности веб-браузеры ограничивают возможность HTTP-запросов к ресурсам, чей домен отличается от домена, на котором запущен выполняющий эти запросы скрипт. CORS («cross-origin resource sharing») это рекомендованый W3C механизм, позволяющий разрешить кросс-доменные запросы к серверу (со стороны сервера). Если вы хотите сделать из обработчика AJAX-запросов некое подобие API для доступа со сторонних ресурсов (какой бы ужасной ни казалась эта идея, этому может найтись применение) — придется или разрешить кросс-доменные запросы к обработчику, добавив заголовок
Access-Control-Allow-Origin
, или реализовать API с помощью JSONp.Если у вас на сайте настроена работа в HTTPS функция admin_url вернёт ссылку с https. Но если на сайте неправильный/истекший SSL-сертификат будет происходить редирект на http-версию сайта и сайт будет просматриваться по протоколу http. В итоге фронтенду достанется ссылка на обработчик в другом протоколе, что, скорее всего, вызовет проблемы. В частности, браузер Firefox, скорее всего, не "обрадуется" этому, решив, что вы пытаетесь делать кросс-доменный запрос.
Поэтому, если вы используете HTTPS, не уверены, что с сертификатом всегда и всё будет в порядке и хотите быть уверены, что даже в случае если он отвалится, сайт будет работать нормально, имеет смысл на фронтенде проверять, соответствует ли протокол ссылки обработчика протоколу страницы, на которой запущен скрипт.
Автор: Илья Андриенко, веб-разработчик DataArt.
Комментарии (5)
GRIDark
19.11.2015 12:38Я, конечно, не могу сказать, что очень хорошо разбираюсь в WP, но искренне не понимаю, почему вы не упомянули о wp_nonce.
Если уж статья об AJAX и WP, то тогда расскажите о методах маломальской защиты.DataArt
19.11.2015 13:51Спасибо за комментарий. wp_nonce упомянуто вот тут:
>> Существует функция check_ajax_referer, которая проверяет реферера (проверяет, откуда произошел запрос) и прерывает выполнение, если реферер какой-то «не такой». Эта функция умеет также проверять nonce. Подробнее можно почитать в соответствующей статье кодекса.
Сначала собирался подробно расписать о применении wp_nonce, но потом решил, что статья, все-таки, про AJAX а не про возможные способы защиты AJAX-бэкенда и ограничился упоминанием предназначенной для этого функции check_ajax_referer (даже оставил ссылку для интересующихся)
А вообще Всяческие number-at-once и контрольные суммы в AJAX-запросах в контексте WP — это тема, достойная отдельной статьи. Если есть интерес — в свободное время с удовольствием напишу/расскажу об этом ;-)
Epsiloncool
22.11.2015 20:26О, для меня это была больная тема, пока я не перевёл свой удобный класс для работы с AJAX под WP и не оформил его как плагин. Теперь никаких запарок — ставлю плагин и вперёд. Обращение из js выглядит так
var data = { ... некие данные ... }; jxAction('название экшена', data);
обработчик в php выглядит так
add_action('jx_название экшена', function($jx){ $data = $jx->data; .. обработка ... $jx->alert('Выдаём alert на стороне клиента'); $jx->variable('varname', 'Задаём значение переменной js на стороне клиента'); $jx->call('myfunc', array('Запускаем функцию js с заданными параметрами')); $jx->... есть ещё пяток полезных методов });
Теперь геморрой, связанный с правильной обработкой ajax в wordpress позади, можно создавать сколь угодно сложные ajax-приложения и не париться насчёт обработки ошибок, кодирования многомерных массивов и проч и т.д. В общем очень удобно.
Всё это GPL и лежит тут: https://wordpress.org/plugins/ajax-manufactory/ Рекомендую попробовать, критика принимается.
goss
Спасибо!
Но, может быть, вы имели в виду, что CORS — это механизм, как раз разрешающий обращаться к ресурсам на других доменах?
DataArt
Да, вы правы, спасибо. Поправили.