شبی که یک اکشن هوک، فروشگاه را نجات داد

زمستان ۱۳۹۷، یک فروشگاه اینترنتی متوسط با حدود هزار محصول را پشتیبانی می‌کردم. مدیر سایت شکایت داشت که «موجودی انبار با سایت نمی‌خواند» و هر روز چند سفارش ناموفق به خاطر محصول ناموجود ثبت می‌شود. رفتم سراغ کد و بعد از دو ساعت بررسی، ریشه را پیدا کردم: افزونه‌ای که موجودی را از سیستم انبار همگام می‌کرد، هیچ‌وقت پس از ثبت سفارش فعال نمی‌شد. کسی که افزونه را نوشته بود، فقط یک cron ساعتی گذاشته بود؛ ولی cron روی سایت کم‌ترافیک گاهی ساعت‌ها دیرتر اجرا می‌شد. راه‌حل، سه خط کد بود: قلاب زدن به اکشن هوک woocommerce_thankyou که پس از هر سفارش موفق اجرا می‌شود. آن سه خط، همگام‌سازی را از «ساعتی یک بار» به «لحظه‌ای» تغییر داد و مشکل حل شد. آن شب به یک باور رسیدم که تا امروز در هر پروژه‌ای همراه من است: اکشن هوک‌ها، سنسورهای وردپرس هستند؛ اگر آن‌ها را درست نصب کنید، سایت به شما می‌گوید چه وقت باید کاری انجام شود.

این مقاله، فهرست مهم‌ترین اکشن هوک‌هایی است که در پروژه‌های واقعی به‌کار می‌برم. اگر با مفهوم پایه آشنا نیستید، پیش از ادامه وردپرس چیست و چگونه شروع کنیم و هوک‌های وردپرس چیست را بخوانید. مکمل این مقاله، تفاوت اکشن و فیلتر در وردپرس، نحوه استفاده از add_action و راهنمای حرفه‌ای کار با هوک‌ها است.

Action Hook چیست؟ یادآوری سریع

اکشن هوک، نقطه‌ای در چرخه اجرای وردپرس است که هسته در آن، به همه اعلام می‌کند «من همین حالا این کار را انجام دادم؛ اگر کسی می‌خواهد در واکنش به آن کاری بکند، الان زمانش است». برخلاف فیلتر که داده‌ای را می‌گیرد و تغییر می‌دهد، اکشن داده‌ای را نه می‌گیرد و نه برمی‌گرداند؛ فقط اعلام رخداد است. به همین دلیل، در callback اکشن، چیزی را return نمی‌کنیم. همین تفکیک ساده، در پروژه‌های بزرگ تفاوت بین کدی که کار می‌کند و کدی که انتظار می‌رود بکند را می‌سازد. توضیح کامل در هوک‌های وردپرس چیست و تفاوت اکشن و فیلتر.

اکشن هوک، مانند آژیرِ ماشین آتش‌نشانی است؛ وقتی شنیده شد، همه می‌دانند وقت انجام کار است، ولی خودِ آژیر، کار را انجام نمی‌دهد.

دسته‌بندی اکشن هوک‌ها بر اساس زمان چرخه

قبل از فهرست، یک نقشه ذهنی. اکشن هوک‌ها در شش لحظه چرخه اجرای وردپرس صدا زده می‌شوند:

  1. لحظه بارگذاری اولیه: muplugins_loaded، plugins_loaded، setup_theme، after_setup_theme، init، wp_loaded.
  2. لحظه پردازش درخواست: parse_request، send_headers، wp، template_redirect.
  3. لحظه رندر خروجی: wp_head، wp_body_open، wp_footer، shutdown.
  4. لحظه ذخیره داده: save_post، edited_term، user_register، profile_update.
  5. لحظه حذف یا انتقال: before_delete_post، deleted_post، trashed_post.
  6. لحظه پیشخوان: admin_init، admin_menu، admin_enqueue_scripts.

این شش لایه، در ساختار هسته وردپرس به تفصیل باز شده‌اند. شناخت این دسته‌بندی، از انتخاب هوک اشتباه جلوگیری می‌کند. در هوک‌ها در توسعه افزونه و هوک‌ها در توسعه قالب نمونه‌های بیشتری از این دسته‌ها آمده است.

هوک‌های راه‌اندازی: init و بعد

پرکاربردترین اکشن هوک وردپرس، init است. این هوک در حدود نیمه چرخه اجرا می‌شود، پس از آن‌که هسته و افزونه‌ها بارگذاری شده‌اند و پیش از آن‌که درخواست پردازش شود. تقریباً همه ثبت‌های ساختاری در این هوک انجام می‌شوند: نوع‌نوشته سفارشی، تاکسونومی، شورت‌کد، اندازه تصویر، نقش سفارشی. نمونه:

add_action( 'init', 'myplugin_register_portfolio_cpt' );

function myplugin_register_portfolio_cpt() {
    register_post_type( 'portfolio', array(
        'public'       => true,
        'has_archive'  => true,
        'show_in_rest' => true,
        'supports'     => array( 'title', 'editor', 'thumbnail' ),
    ) );
}

نکته حیاتی: نوع‌نوشته و تاکسونومی را هرگز روی after_setup_theme یا wp_loaded ثبت نکنید؛ چراکه در آن لحظات، هسته آماده پذیرش ساختار جدید نیست و نتیجه، خطای بی‌سروصدا یا رفتار ناسازگار است. راهنمای کامل در هوک‌های وردپرس، کنترل ترتیب اجرای هوک‌ها و Priority در هوک‌ها آمده است.

هوک‌های پردازش درخواست: wp، template_redirect، send_headers

سه هوک کلیدی که پس از init و پیش از رندر قالب اجرا می‌شوند:

  • wp: پس از تشخیص نوع درخواست و پیش از رندر. جای مناسب برای ریدایرکت‌های مبتنی بر محتوا. راهنما در توابع ریدایرکت وردپرس.
  • template_redirect: درست پیش از انتخاب فایل template. جای مناسب برای محدودسازی دسترسی و ریدایرکت‌های نهایی. یک مثال واقعی از این هوک در بررسی وضعیت ورود کاربر آورده‌ام.
  • send_headers: پیش از ارسال هدرهای HTTP. جای مناسب برای افزودن هدرهای امنیتی یا ریدایرکت‌های سطح هدر. راهنما در هدرهای امنیتی HTTP.

یک قاعده عملی: اگر می‌خواهید بر اساس نوع صفحه تصمیم بگیرید (مثلاً «اگر نوشته تکی است، این کار را بکن»)، از template_redirect استفاده کنید نه init. توابع شرطی در init قابل اعتماد نیستند.

هوک‌های رندر خروجی: wp_head، wp_footer و wp_body_open

سه هوکی که در هر قالب حرفه‌ای باید باشند:

یک — wp_head: درون تگ <head>، برای افزودن متا، اسکریپت، استایل و schema. نمونه:

add_action( 'wp_head', 'mytheme_add_meta_description', 1 );

function mytheme_add_meta_description() {
    if ( ! is_singular() ) {
        return;
    }
    $excerpt = get_the_excerpt();
    if ( $excerpt ) {
        printf(
            '<meta name="description" content="%s" />' . "\n",
            esc_attr( wp_strip_all_tags( $excerpt ) )
        );
    }
}

دو — wp_footer: پیش از بسته‌شدن </body>، برای افزودن اسکریپت‌های پایانی. مهم‌ترین کاربردش، قرار دادن کد رهگیری و افزودن اسکریپت‌هایی است که در انتهای صفحه باید بار شوند. نمونه‌های بیشتر در هوک‌های خروجی قالب و هوک‌ها در توسعه قالب.

سه — wp_body_open: از وردپرس ۵.۲، در آغاز <body>. جای مناسب برای گوگل تگ منیجر و اسکریپت‌های ضروری. اگر قالب شما این هوک را ندارد، حتماً اضافه‌اش کنید. راهنمای استاندارد قالب در ساختار فایل‌های قالب استاندارد.

قالبی که wp_head و wp_footer را حذف کرده، در واقع به همه افزونه‌ها اعلام کرده که «من به شما نیازی ندارم»؛ نتیجه، خرابیِ خاموش است.

هوک‌های ذخیره داده: save_post و مشتقاتش

هنگام ساخت، ویرایش یا انتشار نوشته، وردپرس زنجیره‌ای از اکشن هوک‌ها را صدا می‌زند:

  • pre_post_update — پیش از به‌روزرسانی نوشته موجود.
  • wp_insert_post — هم در ساخت و هم در به‌روزرسانی.
  • save_post — پس از ذخیره نوشته؛ پرکاربردترین هوک این دسته.
  • {$post_type}_save_post — نسخه مخصوص یک نوع‌نوشته خاص؛ مثل portfolio_save_post.
  • transition_post_status — هنگام تغییر وضعیت (از draft به publish و برعکس).
  • publish_post — فقط هنگام انتشار نوشته.

الگوی امن برای save_post که در پروژه‌های واقعی همیشه رعایت می‌کنم:

add_action( 'save_post', 'myplugin_save_meta', 10, 3 );

function myplugin_save_meta( $post_id, $post, $update ) {
    if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
        return;
    }
    if ( wp_is_post_revision( $post_id ) ) {
        return;
    }
    if ( ! current_user_can( 'edit_post', $post_id ) ) {
        return;
    }
    if ( isset( $_POST['my_plugin_nonce'] ) 
         && wp_verify_nonce( $_POST['my_plugin_nonce'], 'my_plugin_save' ) ) {
        $value = sanitize_text_field( wp_unslash( $_POST['my_field'] ?? '' ) );
        update_post_meta( $post_id, '_my_field', $value );
    }
}

سه نکته حیاتی: بررسی autosave، بررسی revision و بررسی نانس. بدون این سه، یا داده ناقص ذخیره می‌شود یا خطر امنیتی می‌سازید. راهنمای کامل در PHP امن در وردپرس، کار با متاباکس‌ها، نانس وردپرس و توابع متادیتا.

هوک‌های کاربر: user_register، profile_update، wp_login

در سایت‌های عضویتی و آموزشی، این هوک‌ها حیاتی هستند:

  • user_register — پس از ثبت‌نام کاربر جدید. مناسب برای ارسال ایمیل خوش‌آمدگویی یا مقدارگذاری متادیتا. راهنما در بررسی وضعیت ورود کاربر.
  • profile_update — پس از به‌روزرسانی پروفایل کاربر توسط خودش یا ادمین. مناسب برای ثبت لاگ یا همگام‌سازی با سیستم بیرونی.
  • wp_login — پس از ورود موفق. مناسب برای ثبت آخرین ورود. راهنما در کار با User Meta.
  • wp_logout — پس از خروج کاربر.
  • delete_user — هنگام حذف کاربر. مناسب برای پاک‌سازی داده‌های مرتبط.

نمونه کاربردی برای ثبت زمان آخرین ورود:

add_action( 'wp_login', 'myplugin_record_last_login', 10, 2 );

function myplugin_record_last_login( $user_login, $user ) {
    update_user_meta( $user->ID, '_last_login', current_time( 'timestamp' ) );
}

راهنمای تکمیلی این دسته در هوک‌های مدیریت کاربران و هوک‌های ورود و ثبت‌نام. برای امنیت ورود، امن‌سازی ورود ادمین وردپرس را ببینید.

هوک‌های پیشخوان: admin_init، admin_menu، admin_enqueue_scripts

در پروژه‌های افزونه‌نویسی، این سه هوک بخش عمده کار شما هستند:

نکته کاربردی: در پیشخوان، همیشه asset را به صفحات محدود کنید؛ افزونه‌ای که در همه صفحات پیشخوان، CSS و JS خودش را لود می‌کند، تجربه مدیریت را کند می‌کند. این نکته را در تأثیر افزونه‌ها بر سرعت و بهینه‌سازی کد وردپرس باز کرده‌ام.

هوک‌های AJAX و REST: wp_ajax و wp_ajax_nopriv

در پروژه‌های AJAX، چهار هوک اصلی وجود دارد:

// برای کاربران لاگین‌شده
add_action( 'wp_ajax_my_action', 'myplugin_ajax_handler' );

// برای مهمان‌ها
add_action( 'wp_ajax_nopriv_my_action', 'myplugin_ajax_handler' );

function myplugin_ajax_handler() {
    check_ajax_referer( 'my_nonce', 'nonce' );
    if ( ! current_user_can( 'edit_posts' ) ) {
        wp_send_json_error( array( 'message' => 'دسترسی غیرمجاز' ), 403 );
    }
    $data = sanitize_text_field( wp_unslash( $_POST['data'] ?? '' ) );
    wp_send_json_success( array( 'result' => $data ) );
}

سه نکته امنیتی: نانس الزامی، capability check الزامی، پاک‌سازی ورودی. اگر endpoint عمومی است و نیازی به لاگین ندارد، فقط wp_ajax_nopriv_ را ثبت کنید. راهنمای کامل در پیاده‌سازی نانس در فرم‌ها، ساخت API اختصاصی و توابع نقش و دسترسی.

هوک‌های حذف و انتقال: before_delete_post و trashed_post

پاک‌سازی داده یتیم، بخشی از نگهداری هر سایت چندساله است:

  • before_delete_post — پیش از حذف کامل نوشته. مناسب برای پاک‌سازی داده مرتبط. راهنمای حذف داده یتیم در پاک‌سازی دیتابیس وردپرس.
  • deleted_post — پس از حذف کامل.
  • trashed_post — هنگام انتقال به زباله‌دان.
  • untrashed_post — هنگام بازگشت از زباله‌دان.
  • delete_user — هنگام حذف کاربر.

الگوی امن پاک‌سازی متادیتای سفارشی هنگام حذف نوشته:

add_action( 'before_delete_post', 'myplugin_cleanup_meta' );

function myplugin_cleanup_meta( $post_id ) {
    if ( 'portfolio' !== get_post_type( $post_id ) ) {
        return;
    }
    global $wpdb;
    $wpdb->delete(
        $wpdb->postmeta,
        array( 'post_id' => $post_id, 'meta_key' => '_my_plugin_data' ),
        array( '%d', '%s' )
    );
}

نکته: وردپرس هنگام حذف نوشته، متادیتای آن را به‌طور خودکار پاک می‌کند؛ ولی جدول‌ها یا داده‌های یتیم در ساختار اختصاصی، باقی می‌مانند. راهنمای کامل در توابع ایجاد و حذف نوشته و بهینه‌سازی کوئری‌های MySQL.

هوک‌های ووکامرس: woocommerce_thankyou و مشابهش

در فروشگاه‌های ووکامرسی، این هوک‌ها بخش زیادی از منطق کسب‌وکار را می‌سازند:

  • woocommerce_new_order — هنگام ایجاد سفارش.
  • woocommerce_order_status_completed — هنگام تغییر وضعیت سفارش به «تکمیل‌شده».
  • woocommerce_thankyou — در صفحه تشکر پس از پرداخت موفق.
  • woocommerce_add_to_cart — هنگام افزودن محصول به سبد.
  • woocommerce_checkout_order_processed — پس از پردازش چک‌اوت.
  • woocommerce_product_set_stock — هنگام تغییر موجودی محصول.

نمونه واقعی از همان تجربه ابتدای مقاله:

add_action( 'woocommerce_thankyou', 'myplugin_sync_inventory', 10, 1 );

function myplugin_sync_inventory( $order_id ) {
    if ( ! $order_id ) {
        return;
    }
    $order = wc_get_order( $order_id );
    foreach ( $order->get_items() as $item ) {
        $product_id = $item->get_product_id();
        $qty        = $item->get_quantity();
        myplugin_reduce_external_stock( $product_id, $qty );
    }
}

راهنمای کامل در هوک‌های ووکامرس، مدیریت سفارش‌های ووکامرس و تنظیم روش‌های پرداخت ووکامرس.

هوک‌های موضوعی در پست و صفحه: the_post، loop_start و loop_end

سه هوکی که کمتر شناخته شده‌اند ولی در قالب‌های حرفه‌ای زیاد به‌کار می‌آیند:

  • the_post — در هر تکرار حلقه، پس از the_post(). برای افزودن محتوای پویا به هر آیتم.
  • loop_start و loop_end — آغاز و پایان حلقه اصلی.
  • get_template_part — قبل از هر فراخوانی template part. راهنمای template part در ساختار فایل‌های قالب استاندارد.

نمونه: افزودن کلاس اختصاصی به نوشته‌های ویژه در حلقه:

add_action( 'the_post', 'mytheme_mark_featured' );

function mytheme_mark_featured() {
    if ( get_post_meta( get_the_ID(), '_featured', true ) ) {
        echo '<span class="featured-badge">ویژه</span>';
    }
}

هوک‌های وردپرس و چرخه اجرای پیشخوان: load-{page}

یکی از پرکاربردترین و کم‌شناخته‌شده‌ترین هوک‌ها، load-{page} است. این هوک پیش از رندر یک صفحه خاص در پیشخوان اجرا می‌شود و برای کارهای پرهزینه مناسب است:

add_action( 'load-post.php', 'myplugin_on_edit_post_screen' );

function myplugin_on_edit_post_screen() {
    // اجرا فقط هنگام ویرایش نوشته
}

کاربردش: انجام کارهایی مثل ساخت دیتابیس کمکی، بارگذاری تنظیمات، یا حتی ریدایرکت پیش از رندر. راهنمای دقیق این هوک در ساخت منوی مدیریتی آمده است.

اولویت و تعداد پارامترها در اکشن هوک‌ها

دو پارامتر کلیدی در add_action:

  • priority: عددی که ترتیب اجرا را تعیین می‌کند؛ کمتر یعنی زودتر. پیش‌فرض ۱۰.
  • accepted_args: تعداد پارامترهایی که callback دریافت می‌کند؛ پیش‌فرض ۱.

نمونه‌ای که در پروژه واقعی زیاد لازم می‌شود: افزودن کد رهگیری به فوتر با اولویت بالاتر از افزونه‌های دیگر تا آخرین نفر اجرا شود:

add_action( 'wp_footer', 'myplugin_tracking_code', 999 );

function myplugin_tracking_code() {
    if ( is_admin() ) {
        return;
    }
    // کد رهگیری
}

راهنمای کامل priority در Priority در هوک‌ها، کنترل ترتیب اجرای هوک‌ها و پارامترهای هوک. برای نمونه‌های بیشتر از accepted_args، نحوه استفاده از add_action را ببینید.

حذف اکشن هوک‌ها: remove_action

گاهی نیاز است اکشنی که افزونه یا قالب دیگری ثبت کرده را حذف کنید. الگوی پایه:

remove_action( 'wp_footer', 'myplugin_add_footer_signature', 10 );

// یا برای متد کلاس‌ها
remove_action( 'init', array( 'My_Class', 'method_name' ), 20 );

سه نکته حیاتی: priority باید مطابق با ثبت باشد؛ حذف باید بعد از ثبت انجام شود؛ callbackهای closure قابل حذف نیستند. راهنمای کامل در حذف اکشن هوک و حذف فیلتر هوک. یک تجربه میدانی: در پروژه‌ای، افزونه‌ای در فوتر یک ویجت اضافه درج می‌کرد که هماهنگ با طراحی نبود. با remove_action و priority درست، در چایلد تم حذف شد. راهنمای چایلد تم در قالب چایلد چیست و توسعه با چایلد تم.

ساخت اکشن سفارشی: قابل توسعه‌کردن افزونه

در پروژه‌های بزرگ، ساخت اکشن سفارشی افزونه شما را به یک پلتفرم تبدیل می‌کند که افزونه‌های دیگر به آن وصل شوند:

// در افزونه شما
do_action( 'myplugin_after_order_created', $order_id, $user_id );

// افزونه دیگر می‌تواند وصل شود
add_action( 'myplugin_after_order_created', 'other_plugin_send_to_crm', 10, 2 );

نکات: پیشوند یکتا در نام هوک، مستندسازی PHPDoc برای پارامترها، و در نظر گرفتن قابلیت حذف. راهنمای کامل در ساخت اکشن سفارشی، کدنویسی اختصاصی افزونه و ساخت قابلیت اختصاصی با هوک‌ها.

دیباگ اکشن هوک‌ها: ابزارها

وقتی اکشنی کار نمی‌کند، سه ابزار در پروژه‌های خودم استفاده می‌کنم:

یک تجربه میدانی: در پروژه‌ای، یک اکشن به‌نظر ساده کار نمی‌کرد. با global $wp_filter، کشف شد که افزونه‌ای دیگر همان callback را با priority بالاتر ثبت کرده و خروجی ما را نادیده می‌گیرد. تنظیم priority، مشکل را حل کرد. راهنمای کامل در تست و دیباگ پروژه‌های وردپرس.

اشتباهات رایج در استفاده از اکشن هوک‌ها

فهرست کامل اشتباهات در اشتباهات رایج هوک‌ها و اشتباهات رایج توسعه وردپرس آمده است.

الگوی حرفه‌ای: Registry Pattern برای اکشن هوک‌ها

در پروژه‌های بزرگ، تمام هوک‌ها را در یک نقطه ثبت کنید:

class My_Plugin_Hooks {
    public static function init() {
        add_action( 'init', array( __CLASS__, 'register_post_type' ) );
        add_action( 'wp_enqueue_scripts', array( __CLASS__, 'enqueue_assets' ) );
        add_action( 'save_post', array( __CLASS__, 'save_meta' ), 10, 3 );
        add_action( 'wp_footer', array( __CLASS__, 'render_tracking', ), 999 );
    }
    // متدها
}
My_Plugin_Hooks::init();

مزیت: خوانایی، امکان تست، و مدیریت ترتیب اجرا. الگوهای مشابه در ساختار فایل‌های افزونه استاندارد، توسعه افزونه از صفر، استانداردهای کدنویسی وردپرس و استفاده از استانداردها در پروژه‌ها. یک تجربه میدانی: در پروژه‌ای با هفت افزونه اختصاصی، انتقال به این الگو، زمان دیباگ تعارض‌ها را نصف کرد.

امنیت در اکشن هوک‌ها

هر اکشن هوک، یک نقطه ورود بالقوه است. سه قاعده امنیتی الزامی:

  1. بررسی دسترسی در callback: پیش از هر عملیات حساس، current_user_can. راهنما در نقش و دسترسی.
  2. نانس در فرم‌ها و AJAX: wp_verify_nonce و check_ajax_referer. راهنما در نانس وردپرس.
  3. پاک‌سازی ورودی و escape خروجی: هر داده از کاربر، sanitize؛ هر داده به HTML، escape. راهنما در پاک‌سازی داده‌ها و PHP امن در وردپرس.

مباحث امنیتی تکمیلی در هوک‌های وردپرس و امنیت کد، امنیت پروژه وردپرس و امنیت وردپرس برای مبتدیان.

جمع‌بندی

مهم‌ترین اکشن هوک‌های وردپرس، در هشت دسته خلاصه می‌شوند: راه‌اندازی (init)، پردازش درخواست (wp، template_redirect)، رندر خروجی (wp_head، wp_footer، wp_body_open)، ذخیره داده (save_post و مشتقاتش)، کاربر (user_register، profile_update، wp_login)، پیشخوان (admin_init، admin_menu)، AJAX (wp_ajax، wp_ajax_nopriv) و ووکامرس (woocommerce_thankyou و مشابهش). سه اصل را در پایان تاکید می‌کنم: اول، اکشن هوک را به‌عنوان سنسور ببینید، نه به‌عنوان دکمه. دوم، در callback اکشن، چیزی return نکنید؛ فقط کار انجام دهید. سوم، امنیت را در هر اکشن جدی بگیرید — نانس، capability و پاک‌سازی.

اگر امروز یک کار در این مسیر انجام می‌دهید: در پروژه فعلی خود، فایل‌های افزونه و قالب را باز کنید و ببینید کدام اکشن هوک‌ها در جای اشتباه قرار گرفته‌اند — مثلاً منطق در قالب، یا ثبت نوع‌نوشته روی هوک اشتباه. همان یک بازبینی، در آپدیت بعدی نجات‌دهنده است. اگر تجربه‌ای از یک اکشن هوک دارید که پروژه‌ای را نجات داد یا باگی را حل کرد، در دیدگاه‌ها بنویسید — همان گزارش‌های واقعی، این راهنما را برای توسعه‌دهنده بعدی دقیق‌تر می‌کند. 🔗