Технологический консалтинг и разработка ПО
142,03
рейтинг
18 ноября 2015 в 19:07

Разработка → Использование 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.
Автор: @DataArt
DataArt
рейтинг 142,03
Технологический консалтинг и разработка ПО

Комментарии (5)

  • 0
    Спасибо!
    Но, может быть, вы имели в виду, что CORS — это механизм, как раз разрешающий обращаться к ресурсам на других доменах?
    • 0
      Да, вы правы, спасибо. Поправили.
  • 0
    Я, конечно, не могу сказать, что очень хорошо разбираюсь в WP, но искренне не понимаю, почему вы не упомянули о wp_nonce.

    Если уж статья об AJAX и WP, то тогда расскажите о методах маломальской защиты.
    • 0
      Спасибо за комментарий. wp_nonce упомянуто вот тут:
      >> Существует функция check_ajax_referer, которая проверяет реферера (проверяет, откуда произошел запрос) и прерывает выполнение, если реферер какой-то «не такой». Эта функция умеет также проверять nonce. Подробнее можно почитать в соответствующей статье кодекса.

      Сначала собирался подробно расписать о применении wp_nonce, но потом решил, что статья, все-таки, про AJAX а не про возможные способы защиты AJAX-бэкенда и ограничился упоминанием предназначенной для этого функции check_ajax_referer (даже оставил ссылку для интересующихся)

      А вообще Всяческие number-at-once и контрольные суммы в AJAX-запросах в контексте WP — это тема, достойная отдельной статьи. Если есть интерес — в свободное время с удовольствием напишу/расскажу об этом ;-)
  • 0
    О, для меня это была больная тема, пока я не перевёл свой удобный класс для работы с 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/ Рекомендую попробовать, критика принимается.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.

Самое читаемое Разработка