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

19 листопада 2015
Илья Андриенко, Developer

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!'));              wp_die();          }      }            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)

CORS это механизм, предотвращающий обращение с веб-страницы к страницам, расположенным на другом домене. Если вы хотите сделать из обработчика AJAX-запросов некое подобие API для браузера (какой бы ужасной ни казалась эта идея) - придется или разрешить кросс-доменные запросы к обработчику, добавив заголовок Access-Control-Allow-Origin, или реализовать JSONp.

Если у вас на сайте настроена работа в HTTPS функция admin_url вернёт ссылку с https. Но если на сайте неправильный/истекший SSL-сертификат будет происходить редирект на http-версию сайта и сайт будет просматриваться по протоколу http. В итоге фронтенду достанется ссылка на обработчик в другом протоколе, что, скорее всего, вызовет проблемы. В частности, браузер Firefox, скорее всего, не "обрадуется" этому, решив, что вы пытаетесь делать кросс-доменный запрос.

Поэтому, если вы используете HTTPS, не уверены, что с сертификатом всегда и всё будет в порядке и хотите быть уверены, что даже в случае если он отвалится, сайт будет работать нормально, имеет смысл на фронтенде проверять, соответствует ли протокол ссылки обработчика протоколу страницы, на которой запущен скрипт.

  • Україна, Remote.UA; Україна, Дніпро; Україна, Київ; Україна, Львів; Україна, Одеса; Україна, Харків; Україна, Херсон
    1 березня