Использование 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
    • 38,2k
    • 5

    DataArt

    109,00

    Технологический консалтинг и разработка ПО

    Поделиться публикацией
    Комментарии 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/ Рекомендую попробовать, критика принимается.

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

          Самое читаемое