DataArt corporate blog
Ajax
CMS
PHP
WordPress
November 2015 18

Использование AJAX-обработчика WordPress



WordPress, будучи одной из самых популярных CMS в мире, снабжен подробной документацией, а точнее, даже двумя. В связи с чем ни в коем случае не стоит воспринимать этот текст как описание неких “best practices” и уж точно никто не заставляет слепо следовать описанному. Статья — просто быстрый ответ на вопрос «как?!» (следующий абзац) и подробное описание всего, что нужно знать чтобы заставить WordPress отвечать на AJAX-запросы (вся остальная статья).

Кратко


Традиционно для AJAX-запросов нужно две вещи: скрипт на сервере (бекенд), который будет отвечать на запросы, и скрипт на клиенте (фронтенд), который будет эти запросы делать. WordPress позволяет делегировать функции на обращение к специальному URL, по которому находится обработчик запросов.

Итак, работает это, «WordPress-way», вот так:

  1. На бекенде с помощью функции admin_url получаем ссылку на обработчик AJAX-запросов и передаем ее во фронтенд одним из способов. Именно к этой ссылке мы будем делать наши запросы.
  2. На бекенде регистрируется хук с функцией для обработки некоего экшена. Назовем этот экшен, например, get_posts.
  3. Фронт-енд делает запросы к 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.
+6
40.8k 113
Comments 5
Top of the day