تابع has_filter یکی از توابع کلیدی وردپرس برای بررسی ثبت بودن یک فیلتر و دریافت priority آن است. این تابع در جلوگیری از حذف ناموفق، اشکال‌زدایی فیلترها و شرط‌گذاری پیشرفته در افزونه‌نویسی نقش جدی دارد. تشخیص درست اینکه یک فیلتر ثبت شده یا نه، می‌تواند از تضاد بین افزونه‌ها و رفتار غیرمنتظره جلوگیری کند. اشتباهات رایجی مانند نبود بررسی قبل از remove_filter و نبود تست می‌تواند به حذف ناخواسته یا ناموفق منجر شود. تسلط بر این تابع برای کدنویسی حرفه‌ای ضروری است و در کنترل فیلترها کاربرد جدی دارد.

چرا بررسی ثبت بودن فیلتر حیاتی است؟

در توسعه افزونه وردپرس، گاهی لازم است یک فیلتر ثبت‌شده توسط قالب یا افزونه دیگری را حذف یا جایگزین کنید. اگر بدون بررسی، remove_filter را فراخوانی کنید و آن فیلتر ثبت نشده باشد، هیچ خطایی نمی‌گیرید اما عملیات شما بی‌اثر می‌ماند. همچنین در اشکال‌زدایی، دانستن اینکه یک فیلتر چه زمانی و با چه اولویتی ثبت شده، می‌تواند به شناسایی تضاد بین افزونه‌ها کمک کند. تابع has_filter() این اطلاعات را فراهم می‌کند.

تابع has_filter چیست؟

تابع has_filter() یک تابع هسته وردپرس است که در فایل wp-includes/plugin.php تعریف شده است. این تابع بررسی می‌کند که آیا یک callback مشخص به یک فیلتر خاص متصل است یا نه. خروجی این تابع به شکل زیر است: - اگر callback مشخص باشد و ثبت شده باشد: مقدار priority آن (عدد صحیح) برمی‌گردد - اگر callback مشخص باشد و ثبت نشده باشد: false برمی‌گردد - اگر callback مشخص نباشد: true اگر هر callbackی به آن فیلتر متصل باشد این رفتار دوگانه، استفاده‌های مختلفی را ممکن می‌کند.

امضای تابع و پارامترها

امضای این تابع به‌شکل زیر است:
function has_filter( $hook_name = '', $callback = false ) {
    global $wp_filter;

    if ( ! isset( $wp_filter[ $hook_name ] ) ) {
        return false;
    }

    return $wp_filter[ $hook_name ]->has_filter( $callback );
}
پارامتر اول (hook_name) نام فیلتر است. پارامتر دوم (callback) اختیاری است و می‌تواند یک تابع، متد کلاس یا closure باشد. نکته مهم: اگر پارامتر اول خالی باشد و پارامتر دوم مشخص، بررسی روی همه فیلترها انجام می‌شود.

سازوکار داخلی تابع

تابع has_filter از متغیر جهانی $wp_filter استفاده می‌کند. این متغیر یک آرایه از تمام فیلترهای ثبت‌شده است که در آن، کلیدها نام فیلترها و مقادیر، شیء WP_Hook هستند. متد has_filter روی این شیء بررسی می‌کند که آیا callback مشخص در آن ثبت شده است یا نه. اگر callback مشخص نباشد، بررسی می‌کند که آیا هر callbackی ثبت شده است. نکته مهم این است که این تابع در هر زمان پس از اجرای هوک init قابل استفاده است. پیش از آن، فیلترهای ثبت‌شده توسط افزونه‌ها ممکن است هنوز کامل نشده باشند.

مقدار بازگشتی و تفسیر آن

سه حالت مختلف برای مقدار بازگشتی وجود دارد:
// بررسی ثبت یک callback مشخص
$priority = has_filter( 'the_content', 'myplugin_content_filter' );
if ( false !== $priority ) {
    // این callback ثبت شده و priority آن در $priority است
}

// بررسی ثبت هر callbackی
$exists = has_filter( 'the_content' );
if ( $exists ) {
    // حداقل یک callback به the_content متصل است
}
نکته مهم: هرگز نباید از if ( has_filter( ... ) ) بدون بررسی دقیق استفاده کنید چرا که priority می‌تواند صفر باشد و در این حالت برخی بررسی‌ها نادرست تفسیر می‌شود. همیشه با false !== $priority بررسی کنید.

کاربرد قبل از remove_filter

یکی از رایج‌ترین کاربردهای این تابع، بررسی پیش از حذف است:
add_action( 'init', 'myplugin_replace_filter' );
function myplugin_replace_filter() {
    if ( has_filter( 'the_content', 'mytheme_custom_content' ) ) {
        remove_filter( 'the_content', 'mytheme_custom_content', 10 );

        add_filter( 'the_content', 'myplugin_replacement_content', 10 );
    }
}
نکته مهم: در remove_filter، اولویت باید دقیقاً با اولویت ثبت‌شده مطابقت داشته باشد. اگر اولویت اشتباه باشد، حذف ناموفق می‌ماند. نمونه دقیق‌تر با استفاده از priority برگردانده‌شده:
function myplugin_safe_remove_filter() {
    $priority = has_filter( 'the_content', 'mytheme_custom_content' );

    if ( false !== $priority ) {
        remove_filter( 'the_content', 'mytheme_custom_content', $priority );
    }
}
این الگو امکان حذف دقیق بدون دانستن اولویت اصلی را فراهم می‌کند.

کاربرد در اشکال‌زدایی

تابع has_filter در اشکال‌زدایی نیز مفید است. اگر می‌خواهید بدانید که یک فیلتر توسط چه افزونه‌ای ثبت شده، می‌توانید از این تابع برای بررسی استفاده کنید:
add_action( 'wp_loaded', 'myplugin_debug_filters' );
function myplugin_debug_filters() {
    if ( ! defined( 'WP_DEBUG' ) || ! WP_DEBUG ) {
        return;
    }

    $filters_to_check = array(
        'the_content',
        'the_title',
        'the_excerpt',
    );

    foreach ( $filters_to_check as $filter ) {
        $exists = has_filter( $filter );

        if ( $exists ) {
            error_log( sprintf( 'Filter %s has callbacks registered', $filter ) );
        }
    }
}
نکته مهم: در محیط تولید، این کدها باید غیرفعال باشند تا حجم لاگ افزایش نیابد.

تفاوت با has_action

از نظر فنی، has_filter و has_action یکسان عمل می‌کنند چرا که در وردپرس، اکشن و فیلتر از یک ساختار مشترک استفاده می‌کنند. تفاوت فقط در نامگذاری و کاربرد معنایی است: - has_filter(): برای بررسی فیلترها استفاده می‌شود - has_action(): برای بررسی اکشن‌ها استفاده می‌شود راهنمای تابع دیگر در صفحه has_action آمده است.

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

اشتباه اول، نبود بررسی پیش از remove_filter است. اگر بدون بررسی حذف کنید و فیلتر وجود نداشته باشد، حذف بی‌اثر می‌ماند. اشتباه دوم، اشتباه در تفسیر مقدار بازگشتی است. همیشه با false !== $result بررسی کنید، نه با if ( $result ). اشتباه سوم، استفاده پیش از هوک init است. اگر تابع شما پیش از init اجرا شود، ممکن است فیلترهای ثبت‌شده توسط افزونه‌های دیگر را نبینید. اشتباه چهارم، نبود توجه به اولویت در remove_filter است. اولویت باید دقیقاً مطابقت داشته باشد. اشتباه پنجم، استفاده از has_filter برای بررسی وجود یک closure است. توابع closure با هر بار ایجاد، یک شناسه جدید می‌گیرند و مقایسه آنها ممکن نیست. برای این سناریو، از متغیر ذخیره‌شده استفاده کنید. اشتباه ششم، نبود تست است. باید در سناریوهای مختلف (فیلتر ثبت شده، ثبت نشده، با اولویت‌های مختلف) کد خود را بررسی کنید.

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

در نگاه مهندسی، تابع has_filter() یک نقطه دسترسی در لایه Hook Registry است که بر چند جنبه از سیستم اثر می‌گذارد. لایه اول لایه Reflection است. این تابع امکان بررسی وضعیت فعلی فیلترها را فراهم می‌کند. لایه دوم لایه Debugging است. در پروژه‌های پیچیده با چند افزونه، تشخیص اینکه کدام افزونه یک فیلتر را ثبت کرده، می‌تواند به حل تضادها کمک کند. لایه سوم لایه Integration Control است. در افزونه‌های همکاری‌کننده، این تابع امکان حذف و جایگزینی دقیق فیلترهای افزونه‌های دیگر را فراهم می‌کند. لایه چهارم لایه Performance است. فراخوانی مکرر این تابع هزینه ناچیزی دارد چرا که تنها یک جستجوی ساده در آرایه انجام می‌دهد. لایه پنجم لایه Order of Execution است. اگر افزونه‌ای فیلترها را در هوک init ثبت کند، افزونه شما باید در اولویت بعدی این بررسی را انجام دهد. لایه ششم لایه Type Safety است. callback می‌تواند تابع، متد کلاس یا closure باشد. برای بررسی دقیق متد کلاس، باید از آرایه array( $object, 'method_name' ) استفاده کنید. لایه هفتم لایه Multisite است. در شبکه‌های Multisite، فیلترها در هر سایت مستقل هستند. لایه هشتم لایه Testing است. تست‌های واحد باید همه سناریوها را پوشش دهند: callback ثبت شده، ثبت نشده، با اولویت‌های مختلف. مفاهیم پایه‌ای Reflection در Reflection در ویکی‌پدیا توضیح داده شده است. برای مطالعه بیشتر روی توابع مرتبط، می‌توانید به راهنمای has_action، راهنمای remove_filter، راهنمای add_filter، راهنمای apply_filters، راهنمای current_filter، راهنمای doing_action و راهنمای did_action مراجعه کنید.

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

تفاوت has_filter و has_action چیست؟ از نظر فنی یکسان عمل می‌کنند؛ تفاوت فقط در نامگذاری و کاربرد معنایی است. چه زمانی مقدار false برمی‌گرداند؟ وقتی callback مشخص باشد و ثبت نشده باشد. آیا priority صفر باعث می‌شود تابع مقدار false برگرداند؟ خیر، صفر یک مقدار معتبر است. با false !== $result بررسی کنید. آیا این تابع پیش از init کار می‌کند؟ ممکن است ناقص باشد چرا که افزونه‌ها هنوز فیلترهای خود را ثبت نکرده‌اند. آیا می‌توان از این تابع برای بررسی closure استفاده کرد؟ خیر، closureها به‌سختی قابل مقایسه هستند. از متغیر ذخیره‌شده استفاده کنید.

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

تابع has_filter() یک ابزار قدرتمند برای بررسی ثبت بودن فیلترها و دریافت priority آنها است. استفاده درست از آن یعنی بررسی دقیق مقدار بازگشتی، استفاده پیش از remove_filter و add_filter، توجه به اولویت و تست در سناریوهای مختلف. این تابع در افزونه‌های همکاری‌کننده و پروژه‌هایی که فیلترهای افزونه‌های دیگر را مدیریت می‌کنند، ارزش بالایی دارد. اگر این تابع را در پروژه‌ای واقعی به کار برده‌اید و رفتار غیرمنتظره‌ای دیده‌اید — به‌خصوص در ترکیب با افزونه‌های پیچیده یا در Child Theme — تجربه‌تان می‌تواند راهگشای دیگران باشد. کدام بخش بیشترین زمان را از شما گرفت؟ دیدگاه خود را بنویسید.