~/icsd.ir — bash
SYSTEM_ONLINE

🎣 فصل ۹: Hooks و Filters – قلب توسعه وردپرس

Hooks (قلاب‌ها) سیستم رویدادمحور وردپرسه که بهت اجازه می‌ده بدون دستکاری کد اصلی، رفتار وردپرس رو تغییر بدی. یاد گرفتن این مفهوم تو رو از یه کاربر معمولی به یه توسعه‌دهنده وردپرس تبدیل می‌کنه.

Hooks (قلاب‌ها) سیستم رویدادمحور وردپرسه که بهت اجازه می‌ده بدون دستکاری کد اصلی، رفتار وردپرس رو تغییر بدی. یاد گرفتن این مفهوم تو رو از یه کاربر معمولی به یه توسعه‌دهنده وردپرس تبدیل می‌کنه.

💡 مفهوم Hook

تصور کن وردپرس یه قطار بزرگه که از ایستگاه‌های مختلف رد می‌شه. توی هر ایستگاه می‌تونی سوار بشی و کاری بکنی – یا حتی محموله رو تغییر بدی.

دو نوع Hook

  • Action: «وقتی این اتفاق افتاد، این کار رو هم بکن» (مثل فایر کردن یه ایمیل)
  • Filter: «وقتی این داده داره رد می‌شه، می‌خوام تغییرش بدم» (مثل کم کردن قیمت محصول)
🎯

قانون ساده

Action = اجرا کردن یه کار. Filter = تغییر دادن یه مقدار.

⚡ Action ها

وردپرس در نقاط کلیدی اجرا، یه action رو «فایر» می‌کنه. تو می‌تونی یه تابع به اون action متصل کنی تا اجرا بشه.

ساختار

add_action
add_action(
    'hook_name',     // اسم هوک
    'callback_func', // تابع callback
    10,              // priority (پیش‌فرض ۱۰)
    1                // تعداد argument (پیش‌فرض ۱)
);

مثال: ارسال ایمیل بعد از انتشار نوشته

functions.php
function notify_admin_new_post( $post_id, $post ) {
    // فقط برای پست‌های منتشر شده
    if ( $post->post_status !== 'publish' ) {
        return;
    }
    
    $admin_email = get_option( 'admin_email' );
    $subject = 'نوشته جدید: ' . $post->post_title;
    $message = "نوشته جدیدی منتشر شد:nn";
    $message .= 'عنوان: ' . $post->post_title . "n";
    $message .= 'لینک: ' . get_permalink( $post_id );
    
    wp_mail( $admin_email, $subject, $message );
}
add_action( 'wp_insert_post', 'notify_admin_new_post', 10, 2 );

Action های مهم چرخه عمر

Action کِی فایر می‌شه؟
plugins_loaded بعد از لود شدن همه افزونه‌ها
after_setup_theme بعد از لود قالب
init وردپرس کاملاً راه‌اندازی شده
wp_loaded کوئری اصلی هنوز اجرا نشده
wp کوئری اصلی اجرا شد
wp_head توی <head> سایت
wp_body_open بلافاصله بعد از <body>
wp_footer قبل از </body>

do_action – فراخوانی یه action

وقتی توسعه‌دهنده‌ای، می‌تونی action سفارشی بسازی که بقیه بهش متصل بشن:

custom action
// در یه افزونه
function my_special_event() {
    // یه کار اصلی...
    
    // به دیگران اجازه بده اینجا چیزی اضافه کنن
    do_action( 'my_plugin_event_happened', $data );
}

// حالا یکی دیگه می‌تونه:
add_action( 'my_plugin_event_happened', function( $data ) {
    // واکنش به این event
} );

🔧 Filter ها

Filter یه مقدار رو می‌گیره، تغییر می‌ده و برمی‌گردونه. همیشه باید مقدار رو return کنی.

ساختار

add_filter
add_filter(
    'filter_name',
    'callback_func',
    10,  // priority
    1    // تعداد argument
);

function callback_func( $value ) {
    // تغییر $value
    return $value;  // ⚠️ حتماً return کن
}

مثال ۱: تغییر متن نوشته

filter content
// اضافه کردن متن به انتهای هر نوشته
function add_signature_to_content( $content ) {
    // فقط در single
    if ( is_single() && in_the_loop() && is_main_query() ) {
        $signature = '<p class="signature">
            نوشته‌شده توسط محمدعلی ناظری | 
            <a href="https://icsd.ir">ICSD</a>
        </p>';
        $content .= $signature;
    }
    return $content;
}
add_filter( 'the_content', 'add_signature_to_content' );

مثال ۲: تغییر طول چکیده

excerpt length
function custom_excerpt_length( $length ) {
    return 30; // ۳۰ کلمه به جای ۵۵
}
add_filter( 'excerpt_length', 'custom_excerpt_length' );

function custom_excerpt_more( $more ) {
    return '... <a href="' . get_permalink() . '">ادامه مطلب</a>';
}
add_filter( 'excerpt_more', 'custom_excerpt_more' );

Filter های پرکاربرد

Filter چی رو فیلتر می‌کنه؟
the_content محتوای نوشته
the_title عنوان نوشته
the_excerpt چکیده
excerpt_length طول چکیده
body_class کلاس‌های body
post_class کلاس‌های نوشته
wp_title عنوان صفحه
upload_mimes فرمت‌های آپلود مجاز
login_redirect ریدایرکت بعد از ورود
comment_text متن دیدگاه

apply_filters – فراخوانی filter

custom filter
// در افزونه‌ت
$price = 100000;
$price = apply_filters( 'my_plugin_price', $price, $product_id );
echo $price;

// حالا یکی دیگه می‌تونه قیمت رو تغییر بده:
add_filter( 'my_plugin_price', function( $price, $product_id ) {
    if ( get_post_meta( $product_id, '_special', true ) ) {
        return $price * 0.9; // ۱۰٪ تخفیف
    }
    return $price;
}, 10, 2 );

🎚 Priority و Arguments

Priority

عددی که ترتیب اجرا رو مشخص می‌کنه. عدد کمتر = اول اجرا. پیش‌فرض ۱۰.

priority
add_action( 'init', 'function_a', 5 );   // اول
add_action( 'init', 'function_b', 10 );  // دوم (پیش‌فرض)
add_action( 'init', 'function_c', 99 );  // آخر

Arguments

تعداد argument هایی که هوک ارسال می‌کنه:

multiple args
// این هوک ۳ argument داره
add_action( 'save_post', 'my_save_handler', 10, 3 );

function my_save_handler( $post_id, $post, $update ) {
    // $post_id: شناسه
    // $post:    آبجکت کامل
    // $update:  آپدیت یا جدید
}

remove_action و remove_filter

گاهی می‌خوای یه هوک قبلی رو حذف کنی:

remove hooks
// حذف emoji های وردپرس
remove_action( 'wp_head', 'print_emoji_detection_script', 7 );
remove_action( 'wp_print_styles', 'print_emoji_styles' );

// حذف فید RSS
remove_action( 'wp_head', 'feed_links', 2 );

// ⚠️ نکته: priority باید دقیقاً همون priority اضافه کردن باشه

⭐ هوک‌های پرکاربرد

برای استایل و اسکریپت

wp_enqueue_scripts
function my_enqueue_assets() {
    wp_enqueue_style( 'main', get_stylesheet_uri() );
    wp_enqueue_script( 'main', '/path/to/script.js' );
}
add_action( 'wp_enqueue_scripts', 'my_enqueue_assets' );

// برای ادمین
add_action( 'admin_enqueue_scripts', 'my_admin_assets' );

// برای صفحه ورود
add_action( 'login_enqueue_scripts', 'my_login_assets' );

برای ذخیره/بروزرسانی نوشته

save_post
function my_save_meta( $post_id ) {
    // جلوگیری از autosave
    if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) return;
    
    // بررسی nonce
    if ( ! isset( $_POST['my_nonce'] ) || 
         ! wp_verify_nonce( $_POST['my_nonce'], 'my_action' ) ) {
        return;
    }
    
    // بررسی دسترسی
    if ( ! current_user_can( 'edit_post', $post_id ) ) return;
    
    // ذخیره
    if ( isset( $_POST['custom_field'] ) ) {
        update_post_meta( $post_id, '_custom_field', 
            sanitize_text_field( $_POST['custom_field'] ) );
    }
}
add_action( 'save_post', 'my_save_meta' );

// فقط برای CPT خاص
add_action( 'save_post_icsd_product', 'my_save_meta' );

برای ورود/خروج کاربر

login hooks
// بعد از ورود
add_action( 'wp_login', function( $user_login, $user ) {
    // لاگ ورود
    error_log( 'User logged in: ' . $user_login );
}, 10, 2 );

// بعد از خروج
add_action( 'wp_logout', function( $user_id ) {
    // پاک کردن کش کاربر
} );

// ریدایرکت بعد از ورود
add_filter( 'login_redirect', function( $redirect, $request, $user ) {
    if ( ! is_wp_error( $user ) && in_array( 'subscriber', $user->roles ) ) {
        return home_url(); // مشترکین به خانه برن
    }
    return $redirect;
}, 10, 3 );

🛠 ساخت هوک سفارشی

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

custom hooks در افزونه
class My_Donation_Plugin {
    
    public function process_donation( $amount, $donor_email ) {
        // قبل از پردازش
        do_action( 'my_donation_before_process', $amount, $donor_email );
        
        // فیلتر مبلغ (مثلاً برای کسر کارمزد)
        $amount = apply_filters( 'my_donation_amount', $amount );
        
        // پردازش اصلی
        $result = $this->send_to_gateway( $amount );
        
        // بعد از پردازش
        do_action( 'my_donation_after_process', $result, $amount, $donor_email );
        
        return $result;
    }
}

// حالا توسعه‌دهنده دیگه می‌تونه:
add_action( 'my_donation_after_process', function( $result, $amount, $email ) {
    // ارسال ایمیل تشکر
    wp_mail( $email, 'تشکر از کمک شما', "مبلغ {$amount} دریافت شد." );
}, 10, 3 );
💡

برای Persian Donate Plus

این الگو دقیقاً برای افزونه‌ت کاربرد داره! می‌تونی هوک‌هایی مثل pdp_before_donation و pdp_after_donation بذاری تا توسعه‌دهنده‌ها بتونن سیستم تو رو extend کنن.

🎯 مثال‌های واقعی

آسان

مثال ۱: تغییر طول رمز عبور پیش‌فرض

functions.php
add_filter( 'random_password', function( $password, $length ) {
    return wp_generate_password( 16, true, false );
}, 10, 2 );

متوسط

مثال ۲: محدودیت آپلود فایل

file size limit
add_filter( 'wp_handle_upload_prefilter', function( $file ) {
    $max_size = 5 * 1024 * 1024; // ۵ مگابایت
    
    if ( $file['size'] > $max_size ) {
        $file['error'] = 'فایل خیلی بزرگه! حداکثر ۵ مگابایت';
    }
    
    return $file;
} );

پیشرفته

مثال ۳: ثبت فعالیت کاربر

activity log
class User_Activity_Logger {
    
    public function __construct() {
        add_action( 'wp_login',          array( $this, 'log_login' ), 10, 2 );
        add_action( 'wp_logout',         array( $this, 'log_logout' ) );
        add_action( 'save_post',         array( $this, 'log_post_save' ), 10, 2 );
        add_action( 'wp_trash_post',     array( $this, 'log_post_trash' ) );
        add_action( 'comment_post',      array( $this, 'log_comment' ) );
    }
    
    private function log( $action, $data = array() ) {
        $user = wp_get_current_user();
        $log_entry = array(
            'time'    => current_time( 'mysql' ),
            'user_id' => $user->ID ?? 0,
            'user'    => $user->user_login ?? 'guest',
            'action'  => $action,
            'data'    => $data,
            'ip'      => $_SERVER['REMOTE_ADDR'] ?? '',
        );
        
        $logs = get_option( 'user_activity_log', array() );
        $logs[] = $log_entry;
        
        // فقط ۱۰۰۰ ورودی آخر
        if ( count( $logs ) > 1000 ) {
            $logs = array_slice( $logs, -1000 );
        }
        
        update_option( 'user_activity_log', $logs );
    }
    
    public function log_login( $login, $user ) {
        $this->log( 'login', array( 'user' => $login ) );
    }
    
    public function log_logout() {
        $this->log( 'logout' );
    }
    
    public function log_post_save( $post_id, $post ) {
        if ( wp_is_post_revision( $post_id ) ) return;
        $this->log( 'save_post', array(
            'post_id' => $post_id,
            'title'   => $post->post_title,
        ) );
    }
    
    public function log_post_trash( $post_id ) {
        $this->log( 'trash_post', array( 'post_id' => $post_id ) );
    }
    
    public function log_comment( $comment_id ) {
        $this->log( 'comment', array( 'comment_id' => $comment_id ) );
    }
}

new User_Activity_Logger();

📝 خلاصه فصل

  • Action برای اجرای کار، Filter برای تغییر مقدار
  • Filter حتماً مقدار رو return کنه
  • priority پایین‌تر = اول اجرا
  • هوک‌های مهم: init، wp_head، wp_footer، save_post
  • توی افزونه‌ت hook بذار تا extensible باشه
  • برای بررسی هوک‌های موجود: developer.wordpress.org/reference/hooks

نمایش سایت

رنگ سایت
حالت نمایش
اندازهٔ متن
خوانایی

این تنظیمات فقط روی مرورگر شما ذخیره می‌شود.