sd shadow-docs
GitHub
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 wp_send_json_success / wp_send_json_error, nevis echo json_encode()
Abas funkcijas automātiski uzstāda 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));
});