shadow-docs / WordPress / AJAX un nonce
AJAX un nonce pārbaude
WordPress AJAX vienmēr iet caur vienu un to pašu URL — admin-ajax.php. Kurš PHP kods izpildās, nosaka action parametrs pieprasījumā. Drošību nodrošina nonce — vienreizējs, laikā ierobežots tokens.
1. Divi hooki katrai AJAX darbībai
Jāreģistrē divi hooki, ja pieprasījumu var sūtīt arī neielogojies lietotājs (piem. groza darbības viesim):
add_action( 'wp_ajax_asg_add_course_to_cart', 'asg_ajax_add_course_to_cart' ); // ielogojies
add_action( 'wp_ajax_nopriv_asg_add_course_to_cart', 'asg_ajax_add_course_to_cart' ); // viesis
Bieža kļūda
Ja aizmirsti wp_ajax_nopriv_ versiju, pieprasījums no neielogota lietotāja vienkārši neizsauc funkciju vispār — WordPress atgriež tukšu 0 atbildi, bez kļūdas paziņojuma. Tas parasti izskatās pēc "AJAX nestrādā", nevis pēc tiesību problēmas.
2. Nonce: kā tas nonāk no PHP uz JS
Nonce tiek izveidots PHP pusē ar wp_create_nonce() un nodots JavaScript pusei caur wp_localize_script() (sk. tēmas uzstādīšanas lapu):
wp_localize_script( 'asg-scripts', 'asgData', array(
'ajaxUrl' => admin_url( 'admin-ajax.php' ),
'nonce' => wp_create_nonce( 'asg_nonce' ),
) );
Serverī pieprasījuma sākumā to pārbauda check_ajax_referer() — pirmais parametrs jāsakrīt ar to, ko izmantoji wp_create_nonce():
function asg_ajax_add_course_to_cart() {
check_ajax_referer( 'asg_nonce', 'nonce' ); // izbeidz izpildi ar 403, ja nonce nederīgs
if ( ! is_user_logged_in() ) {
wp_send_json_error( array( 'reason' => 'not_logged_in' ) );
}
$product_id = isset( $_POST['product_id'] ) ? absint( $_POST['product_id'] ) : 0;
if ( ! $product_id ) {
wp_send_json_error( array( 'reason' => 'invalid_product' ) );
}
$added = WC()->cart->add_to_cart( $product_id );
if ( $added ) {
$cart_items = array();
foreach ( WC()->cart->get_cart() as $item ) {
$prod = $item['data'];
$cart_items[] = array(
'name' => $prod->get_name(),
'qty' => $item['quantity'],
'price' => WC()->cart->get_product_price( $prod ),
);
}
wp_send_json_success( array(
'items' => $cart_items,
'total' => WC()->cart->get_cart_total(),
'checkout_url' => wc_get_checkout_url(),
) );
} else {
wp_send_json_error( array( 'reason' => 'add_failed' ) );
}
}
Kāpēc
Abas funkcijas automātiski uzstāda wp_send_json_success / wp_send_json_error, nevis echo json_encode()Content-Type: application/json galveni un — svarīgi — izsauc wp_die() pēc izvades. Bez tā PHP process turpina strādāt un WordPress pievieno papildu izvadi (piem. 0) pēc tavas JSON atbildes, kas to sabojā priekšgala pusē.
3. Priekšgala JS puse — fetch() pret admin-ajax.php
function addCourseToCart(productId) {
return fetch(asgData.ajaxUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
action: 'asg_add_course_to_cart',
product_id: productId,
nonce: asgData.nonce,
}),
})
.then((res) => res.json())
.then((json) => {
if (!json.success) {
console.error('Grozam nepievienoja:', json.data.reason);
return;
}
console.log('Grozā:', json.data.items, 'Kopā:', json.data.total);
});
}
Pilns, minimāls piemērs (bez WooCommerce)
Tas pats modelis darbojas jebkurai AJAX darbībai — šis piemērs neprasa WooCommerce un ir pilnībā palaižams jaunā spraudnī vai functions.php:
<?php
// --- PHP: reģistrē un apstrādā ---
add_action( 'wp_ajax_sd_save_note', 'sd_save_note' );
add_action( 'wp_ajax_nopriv_sd_save_note', 'sd_save_note' );
function sd_save_note() {
check_ajax_referer( 'sd_nonce', 'nonce' );
$note = isset( $_POST['note'] ) ? sanitize_text_field( wp_unslash( $_POST['note'] ) ) : '';
if ( '' === $note ) {
wp_send_json_error( array( 'reason' => 'empty_note' ) );
}
update_option( 'sd_last_note', $note );
wp_send_json_success( array( 'saved' => $note ) );
}
// --- Enqueue + nonce nodošana JS pusei ---
add_action( 'wp_enqueue_scripts', function () {
wp_enqueue_script( 'sd-notes', get_template_directory_uri() . '/assets/js/notes.js', array(), '1.0.0', true );
wp_localize_script( 'sd-notes', 'sdData', array(
'ajaxUrl' => admin_url( 'admin-ajax.php' ),
'nonce' => wp_create_nonce( 'sd_nonce' ),
) );
} );
// assets/js/notes.js
document.getElementById('save-note-btn')?.addEventListener('click', () => {
const note = document.getElementById('note-input').value;
fetch(sdData.ajaxUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({ action: 'sd_save_note', note, nonce: sdData.nonce }),
})
.then((res) => res.json())
.then((json) => console.log(json.success ? 'Saglabāts' : json.data.reason));
});