AJAX в админке. Безопасность
Урок 3
Если вы пишете асинхронные запросы для админки WordPress, а не для самого сайта, то вам следует знать несколько принципиальных отличий. В этом уроке я расскажу про эти отличия, а также поговорим за безопасность.
Чтобы скачать готовый код из этого урока, вам нужно купить курс.
Если вы не хоатите использовать отдельный JS-файл для ваших запросов, то вы можете вставить JavaScript-код непосредственно в HTML админки:
admin_head
– внутри тегов </head>
,admin_footer
– перед закрывающим тегом </body>
.Например:
add_action( 'admin_head', 'true_javascript_in_admin', 25 ); function true_javascript_in_admin() { echo "<script>alert( 'привет' )</script>"; }
В том случае, если вы готовы создать отдельный JS-файл для вашего запроса, например назовём его true-admin-ajax.js
, то его можете подключить хуком admin_enqueue_scripts
.
add_action( 'admin_enqueue_scripts', 'true_js_in_admin', 25 ); function true_js_in_admin() { wp_enqueue_script( 'true-adminajax', get_stylesheet_directory_uri() . '/true-admin-ajax.js', array( 'jquery' ) ); }
В предыдущих уроках мы сами динамически выводили URL AJAX-запроса и передавали его в скрипт функцией wp_localize_script(). В админке же ничего этого делать не нужно, URL обработчика AJAX всегда доступен в переменной ajaxurl
.
jQuery.ajax({ type : 'POST', url : ajaxurl, data : 'action=myaction&input_val=' + my_value, success : function(data){ jQuery('#response').html(data); } });
Опять-таки, в предыдущих уроках для написания обработчика AJAX-запроса мы использовали несколько хуков, а именно:
wp_ajax_
– для авторизованных пользователей.wp_ajax_nopriv_
– для неавторизованных пользователей.Как вы думаете, понадобится ли нам в админке второй хук? Конечно же нет, ведь в админке бывают только авторизованные пользователи!
// wp_ajax_ - только для зарегистрированных пользователей add_action( 'wp_ajax_myaction', 'true_function' ); // wp_ajax_{значение параметра action} function true_function(){ update_option( 'my_settings', sanitize_text_field( $_POST[ 'input_val' ] ) ); echo 'Привет, значение ' . esc_html( $_POST[ 'input_val' ] ) . ' сохранено.'; die(); }
На самом деле это нужно не только в админке, но самое время о них сейчас поговорить.
В общем для того, чтобы ваши AJAX-запросы не выполнил кто-то, кто их не должен выполнять, то вам следует их защитить. Для этого в WordPress существует механизм WP Nonces (одноразовых чисел).
Одноразовые числа уникальны в зависимости от сессии пользователя, поэтому запрос не сможет выполнить кто-то левый.
При создании одноразовых чисел можете воспользоваться одной из следующих функций:
wp_nonce_field()
– эта функция сразу же выведет поле типа «hidden» и запишет в него одоразовое число, например wp_nonce_field( 'hello' )
выведет <input type="hidden" name="_wpnonce" value="значение" />
.wp_create_nonce()
– она лишь создаёт одноразовое число и возвращает его в виде строки.Например, если вы передаёте данные в запросе через $('#form').serialize()
, то вам больше подойдёт функция wp_nonce_field()
.
Для проверки правильности одноразового числа в самое начало кода нашего запроса мы вставляем функцию check_ajax_referer()
, в качестве первого параметра передаём секретный ключ одноразового числа.
Подробнее про это всё в видео.
Предположим, в нашем интернет-магазине прямо-таки дофига товаров и при этом приходится часто изменять их цены. Конечно, в таком случае будет удобнее, если редактируемое поле с ценой будет непосредственно в списке товаров, то есть, чтобы можно было изменять цену, не переходя на страницу редактирования.
На самом деле есть два варианта решения, но мы рассмотрим только тот, который по теме.
Более подробно о колонках в списках постов я писал здесь, кстати, возможно плагин магазина, который вы используете (если используете) уже мог сам надобавлять колонок, тогда вам потребуется лишь немного отредактировать их содержимое.
Вот готовый код для колонок.
Обязательно убедитесь, что тип поста на 16-й строчке кода соответствует вашему (слово product
)!
function true_add_post_columns($my_columns){ $slider = array( 'price' => 'Цена' ); $my_columns = array_slice( $my_columns, 0, 5, true ) + $slider + array_slice( $my_columns, 5, NULL, true ); return $my_columns; } function true_fill_post_columns( $column ) { global $post; switch ( $column ) { case 'price': { echo '<input type="text" class="this_price" data-id="' . $post->ID .'" data-nonce="' . wp_create_nonce( 'security12345' ) . '" value="' . intval( get_post_meta( $post->ID, '_price', true ) ) . '" /><p></p>'; break; } } } add_filter( 'manage_edit-product_columns', 'true_add_post_columns', 10, 1 ); // manage_edit-{тип поста}_columns add_action( 'manage_posts_custom_column', 'true_fill_post_columns', 10, 1 );
Не знаете, куда вставлять код?
У вас должно получиться что-то в этом роде:
По скриншоту виду, что никаких кнопок типа «Сохранить» к полю добавлено не было — на самом деле зачем они нам, если цену можно сохранять автоматически (при снятии фокуса), достаточно лишь кликнуть мышью за пределами поля ввода.
Самый легкий шаг на самом деле, но при этом он очень важен. Итак, сначала нужно создать какой-нибудь пустой js-файл в папке с вашей текущей темой (важно, чтобы он находился в той же папке, что и functions.php
, style.css
). Назовите его как-нибудь, у меня это будет ajax-post-meta.js
.
Теперь в functions.php
добавим:
add_action( 'admin_enqueue_scripts', 'true_admin_scripts', 25 ); function true_admin_scripts() { wp_enqueue_script( 'ajaxpostmeta', get_stylesheet_directory_uri() . '/ajax-post-meta.js', array( 'jquery' ) ); }
Если до этого шага вы делали всё в точности так, как я описывал, можете вставлять следующий код в ajax-post-meta.js
, не задумываясь:
jQuery(function($){ $('.this_price').blur(function(){ thisPrice = $(this); $.ajax({ type : 'POST', url : ajaxurl, data : { action : 'updateprice', price_val : thisPrice.val(), product_id : thisPrice.data('id'), _wpnonce : thisPrice.data('nonce') }, beforeSend:function(xhr){ thisPrice.attr('readonly','readonly').next().html('Сохраняю...'); }, success:function(results){ thisPrice.removeAttr('readonly').next().html('<span style="color:#0FB10F">Сохранено</span>'); } }); }); });
В functions.php
:
function updatePrice_callback(){ // название не имеет значения, но должно соответствовать названиям в хуках check_ajax_referer( 'security12345' ); update_post_meta( $_POST[ 'product_id' ], '_price', intval( $_POST[ 'price_val' ] ) ); die(); } add_action( 'wp_ajax_updateprice', 'updatePrice_callback' ); // wp_ajax_nopriv_ не нужен, так как мы работаем в админке (а в админку не попадают незареганные пользователи)
В итоге у вас всё должно получиться, то есть сначала вводим цену в поле, потом щелкаем где-нибудь на странице (например переходим в другое поле), и цена сохраняется.