هوک wp_ajax_ یکی از کلیدی‌ترین هوک‌های وردپرس برای پردازش درخواست‌های AJAX کاربران وارد‌شده و مهمان است. این هوک امکان اتصال هندلرهای سفارشی را برای عملیات‌هایی مانند ذخیره تنظیمات، بارگذاری محتوا و پردازش فرم فراهم می‌کند. طراحی امن این هوک با بررسی nonce، capability و escape داده‌ها، پایه پیاده‌سازی AJAX حرفه‌ای محسوب می‌شود. اشتباهات رایجی مانند نبود nonce، نبود capability، نبود escape و نبود تست می‌تواند به حفره‌های امنیتی جدی و نفوذ منجر شود. تسلط بر این هوک برای افزونه‌نویسی حرفه‌ای ضروری است و در امنیت AJAX کاربرد جدی دارد.

چرا امنیت AJAX حیاتی است؟

درخواست‌های AJAX در وردپرس از دو طریق ارسال می‌شوند: از طریق فایل admin-ajax.php و از طریق REST API. فایل admin-ajax.php یک نقطه ورودی عمومی است که هر کاربری (حتی مهمان) می‌تواند به آن درخواست ارسال کند. اگر هندلرهای AJAX شما به‌درستی محافظت نشوند، هر کاربر خارجی می‌تواند با ارسال درخواست‌های ساختگی، عملیات دلخواه اجرا کند. این پدیده در امنیت به CSRF (Cross-Site Request Forgery) شناخته می‌شود. هوک wp_ajax_ نقطه اتصال هندلرها است و رعایت اصول امنیتی در آن، پایه حفاظت از سایت است.

هوک wp_ajax_ چیست؟

هوک wp_ajax_ یک اکشن هوک داینامیک در وردپرس است که توسط فایل admin-ajax.php فراخوانی می‌شود. این هوک برای پردازش درخواست‌های AJAX کاربران وارد‌شده استفاده می‌شود. ساختار نام این هوک به‌شکل زیر است:
wp_ajax_{$action}
که {$action} نام عملیاتی است که در درخواست AJAX به‌عنوان پارامتر action ارسال می‌شود. نکته مهم این است که این هوک تنها برای کاربران وارد‌شده اجرا می‌شود. برای کاربران مهمان، باید از هوک wp_ajax_nopriv_ استفاده کنید.

تفاوت wp_ajax_ و wp_ajax_nopriv_

دو هوک جداگانه برای پردازش AJAX وجود دارد: - wp_ajax_{$action}: تنها برای کاربران وارد‌شده - wp_ajax_nopriv_{$action}: تنها برای کاربران مهمان اگر تنها wp_ajax_ ثبت شود، درخواست از مهمان‌ها با خطای ۴۰۰ یا پاسخ صفر برمی‌گردد. اگر تنها wp_ajax_nopriv_ ثبت شود، درخواست از کاربران وارد‌شده بی‌پاسخ می‌ماند. برای پشتیبانی از هر دو، باید هر دو هوک ثبت شوند:
add_action( 'wp_ajax_myplugin_load', 'myplugin_load_handler' );
add_action( 'wp_ajax_nopriv_myplugin_load', 'myplugin_load_handler' );
function myplugin_load_handler() {
    // هندلر مشترک
}
نکته مهم: در هندلر مشترک، باید توجه داشته باشید که برخی عملیات برای مهمانان ممنوع است. راهنمای هوک دیگر در صفحه wp_ajax_nopriv آمده است.

ساختار و پارامترها

ساختار پایه استفاده از این هوک:
add_action( 'wp_ajax_myplugin_save', 'myplugin_save_handler' );
function myplugin_save_handler() {
    // بررسی امنیت
    // پردازش درخواست
    // ارسال پاسخ
    wp_send_json_success( $data );
}
این هوک پارامتر ورودی نمی‌گیرد. تمام داده‌ها از طریق متغیرهای جهانی $_POST، $_GET و $_REQUEST قابل دسترسی هستند. نکته مهم: پارامتر action نباید استفاده شود چرا که وردپرس از آن برای مسیریابی استفاده می‌کند.

بررسی nonce

اولین و مهم‌ترین گام امنیتی، بررسی nonce است. nonce یک توکن یک‌بارمصرف است که توسط وردپرس تولید می‌شود و ثابت می‌کند درخواست از طرف خود کاربر ارسال شده است. روش اول، استفاده از check_ajax_referer:
function myplugin_save_handler() {
    check_ajax_referer( 'myplugin_save_nonce', 'nonce' );

    // ادامه پردازش
}
این تابع، اگر nonce نامعتبر باشد، به‌صورت خودکار پاسخ -1 با کد وضعیت ۴۰۳ ارسال می‌کند و پردازش را متوقف می‌سازد. راهنمای این تابع در صفحه check_ajax_referer آمده است. روش دوم، استفاده از wp_verify_nonce:
function myplugin_save_handler() {
    if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( $_POST['nonce'], 'myplugin_save_nonce' ) ) {
        wp_send_json_error( array(
            'code'    => 'invalid_nonce',
            'message' => 'درخواست نامعتبر است',
        ), 403 );
    }

    // ادامه پردازش
}
این روش کنترل بیشتری بر روی پیام خطا می‌دهد. راهنمای این تابع در صفحه wp_verify_nonce آمده است. تولید nonce در سمت PHP و پاس دادن آن به JavaScript:
wp_localize_script(
    'myplugin-script',
    'mypluginData',
    array(
        'ajaxUrl' => admin_url( 'admin-ajax.php' ),
        'nonce'   => wp_create_nonce( 'myplugin_save_nonce' ),
    )
);
راهنمای wp_localize_script در صفحه wp_localize_script آمده است.

بررسی دسترسی کاربر

پس از بررسی nonce، باید بررسی کنید که آیا کاربر جاری مجاز به انجام این عملیات است. برای این کار از current_user_can استفاده کنید:
function myplugin_delete_post_handler() {
    check_ajax_referer( 'myplugin_delete_nonce', 'nonce' );

    if ( ! current_user_can( 'delete_posts' ) ) {
        wp_send_json_error( array(
            'code'    => 'insufficient_permissions',
            'message' => 'دسترسی کافی ندارید',
        ), 403 );
    }

    $post_id = isset( $_POST['post_id'] ) ? absint( $_POST['post_id'] ) : 0;
    if ( ! $post_id ) {
        wp_send_json_error( array( 'message' => 'شناسه نامعتبر' ), 400 );
    }

    $deleted = wp_delete_post( $post_id, true );
    if ( ! $deleted ) {
        wp_send_json_error( array( 'message' => 'حذف ناموفق بود' ), 500 );
    }

    wp_send_json_success( array( 'post_id' => $post_id ) );
}
add_action( 'wp_ajax_myplugin_delete_post', 'myplugin_delete_post_handler' );
نکته مهم: بررسی current_user_can باید پیش از هر عملیات تغییر داده انجام شود. راهنمای این تابع در صفحه current_user_can و صفحه is_user_logged_in آمده است.

کاربردهای عملی در افزونه

ذخیره یادداشت خصوصی کاربر:
add_action( 'wp_ajax_myplugin_save_note', 'myplugin_save_note_handler' );
function myplugin_save_note_handler() {
    check_ajax_referer( 'myplugin_note_nonce', 'nonce' );

    if ( ! is_user_logged_in() ) {
        wp_send_json_error( array( 'message' => 'ابتدا وارد شوید' ), 401 );
    }

    $user_id = get_current_user_id();
    $note = isset( $_POST['note'] ) ? sanitize_textarea_field( $_POST['note'] ) : '';

    if ( empty( $note ) ) {
        wp_send_json_error( array( 'message' => 'یادداشت خالی است' ), 400 );
    }

    update_user_meta( $user_id, 'myplugin_note', $note );

    wp_send_json_success( array(
        'message' => 'یادداشت ذخیره شد',
        'note'    => $note,
    ) );
}
پردازش فرم تماس برای همه کاربران:
add_action( 'wp_ajax_myplugin_contact', 'myplugin_contact_handler' );
add_action( 'wp_ajax_nopriv_myplugin_contact', 'myplugin_contact_handler' );
function myplugin_contact_handler() {
    check_ajax_referer( 'myplugin_contact_nonce', 'nonce' );

    // محدودیت نرخ ارسال
    $ip = myplugin_get_client_ip();
    $key = 'myplugin_contact_' . md5( $ip );
    $count = (int) get_transient( $key );

    if ( $count >= 5 ) {
        wp_send_json_error( array( 'message' => 'تعداد درخواست‌ها بیش از حد مجاز است' ), 429 );
    }
    set_transient( $key, $count + 1, HOUR_IN_SECONDS );

    $name = isset( $_POST['name'] ) ? sanitize_text_field( $_POST['name'] ) : '';
    $email = isset( $_POST['email'] ) ? sanitize_email( $_POST['email'] ) : '';
    $message = isset( $_POST['message'] ) ? sanitize_textarea_field( $_POST['message'] ) : '';

    $errors = array();
    if ( empty( $name ) ) {
        $errors['name'] = 'نام الزامی است';
    }
    if ( ! is_email( $email ) ) {
        $errors['email'] = 'ایمیل نامعتبر است';
    }
    if ( empty( $message ) ) {
        $errors['message'] = 'پیام الزامی است';
    }

    if ( ! empty( $errors ) ) {
        wp_send_json_error( array(
            'message' => 'لطفاً خطاها را بررسی کنید',
            'errors'  => $errors,
        ), 422 );
    }

    wp_mail(
        get_option( 'admin_email' ),
        'پیام جدید از ' . $name,
        $message
    );

    wp_send_json_success( array( 'message' => 'پیام شما ارسال شد' ) );
}
نکته مهم: برای پیشگیری از سوءاستفاده، محدودیت نرخ ارسال (Rate Limiting) ضروری است. این الگو از ارسال پیام‌های اسپم جلوگیری می‌کند.

Escape در پاسخ

داده‌های پاس داده‌شده به wp_send_json_success یا wp_send_json_error به‌صورت JSON ارسال می‌شوند و در سمت JavaScript قابل دسترسی هستند. اگر این داده‌ها در HTML چاپ شوند، باید به‌درستی escape شوند. سمت PHP:
wp_send_json_success( array(
    'title'   => esc_html( $post_title ),
    'content' => wp_kses_post( $post_content ),
    'url'     => esc_url( get_permalink( $post_id ) ),
) );
سمت JavaScript، برای نمایش امن:
// روش اشتباه
element.innerHTML = response.data.title;

// روش صحیح
element.textContent = response.data.title;
نکته مهم: ترکیب escape در سمت PHP با استفاده از textContent در JavaScript، امنیت کامل را تضمین می‌کند. راهنمای esc_html در صفحه esc_html آمده است.

نکات امنیتی و اشتباهات رایج

اشتباه اول، نبود nonce است. اگر nonce بررسی نشود، هر کاربر خارجی می‌تواند درخواست ارسال کند. اشتباه دوم، نبود current_user_can است. اگر دسترسی بررسی نشود، هر کاربر وارد‌شده می‌تواند عملیات حساس را اجرا کند. اشتباه سوم، نبود escape در پاسخ است. اگر داده کاربر در پاسخ ارسال شود و در HTML چاپ شود، حفره XSS ایجاد می‌شود. اشتباه چهارم، استفاده از $_REQUEST است. این متغیر هم $_GET و هم $_POST و هم $_COOKIE را شامل می‌شود و می‌تواند منبع حمله باشد. همیشه از $_POST یا $_GET صریح استفاده کنید. اشتباه پنجم، نبود sanitization داده است. هر داده ورودی باید با توابع مناسب پاک‌سازی شود: - sanitize_text_field برای متن ساده - sanitize_email برای ایمیل - sanitize_url برای URL - absint برای عدد صحیح - wp_kses_post برای HTML اشتباه ششم، نبود Rate Limiting است. بدون محدودیت نرخ، هر کاربر می‌تواند با درخواست‌های متعدد، سرور را از پا درآورد. اشتباه هفتم، نبود تست امنیتی است. باید بررسی کنید که بدون nonce، با nonce نامعتبر و با کاربر غیرمجاز، رفتار درست دارد.

تحلیل فنی پیشرفته

در نگاه مهندسی، هوک wp_ajax_ یک نقطه معماری در لایه AJAX Gateway است که بر چند جنبه از سیستم اثر می‌گذارد. لایه اول لایه Routing است. فایل admin-ajax.php با دریافت پارامتر action، هوک مناسب را فراخوانی می‌کند. این ساختار داینامیک باعث می‌شود که هوک‌های متعدد بدون تداخل کار کنند. لایه دوم لایه Authentication است. وردپرس به‌طور خودکار تشخیص می‌دهد که کاربر وارد‌شده است یا نه و هوک مناسب را فراخوانی می‌کند. لایه سوم لایه Authorization است. پس از احراز هویت، باید مجوزدهی با current_user_can انجام شود. لایه چهارم لایه CSRF Protection است. nonce از حملات CSRF جلوگیری می‌کند. لایه پنجم لایه Input Validation است. همه داده‌های ورودی باید پاک‌سازی شوند. لایه ششم لایه Rate Limiting است. محدودیت نرخ، از حملات DoS و اسپم جلوگیری می‌کند. لایه هفتم لایه Response Contract است. پاسخ باید با ساختار استاندارد JSON برگردانده شود. لایه هشتم لایه Observability است. لاگ‌گیری از درخواست‌های AJAX می‌تواند به شناسایی حملات کمک کند. لایه نهم لایه Multisite است. در شبکه‌های Multisite، هندلرها در هر سایت مستقل اجرا می‌شوند. لایه دهم لایه Testing است. تست‌های امنیتی باید همه سناریوها را پوشش دهند. مفاهیم پایه‌ای CSRF در Cross-site request forgery در ویکی‌پدیا توضیح داده شده است. برای مطالعه بیشتر روی توابع مرتبط، می‌توانید به راهنمای هوک wp_ajax_nopriv، راهنمای wp_send_json_success، راهنمای wp_send_json_error، راهنمای check_ajax_referer، راهنمای wp_verify_nonce، راهنمای wp_localize_script، راهنمای current_user_can و راهنمای is_user_logged_in مراجعه کنید.

پرسش‌های پرتکرار

تفاوت wp_ajax_ و wp_ajax_nopriv_ چیست؟ اولی برای کاربران وارد‌شده و دومی برای مهمان‌ها. آیا باید هر دو هوک را ثبت کرد؟ اگر می‌خواهید هر دو گروه کاربران بتوانند درخواست ارسال کنند، بله. آیا nonce باید در هر درخواست جدید ارسال شود؟ بله، اما nonce تولیدشده معمولاً تا ۲۴ ساعت معتبر است. چطور از حملات Brute Force به AJAX جلوگیری کنیم؟ با Rate Limiting و بررسی nonce. آیا استفاده از $_REQUEST در AJAX توصیه می‌شود؟ خیر، از $_POST یا $_GET صریح استفاده کنید.

نتیجه و مسیر ادامه

هوک wp_ajax_ نقطه اصلی پردازش درخواست‌های AJAX در وردپرس است. استفاده درست از آن یعنی بررسی nonce، بررسی دسترسی، sanitize و escape داده‌ها، Rate Limiting و ارسال پاسخ استاندارد. اشتباه‌های کوچک در این هوک اغلب به حفره‌های امنیتی جدی منجر می‌شوند که می‌توانند سایت را در معرض نفوذ قرار دهند. اگر این هوک را در پروژه‌ای واقعی به کار برده‌اید و رفتار غیرمنتظره‌ای دیده‌اید — به‌خصوص در ترکیب با افزونه‌های امنیتی یا در REST API — تجربه‌تان می‌تواند راهگشای دیگران باشد. کدام بخش بیشترین زمان را از شما گرفت؟ دیدگاه خود را بنویسید.