آن روزی که فراموش‌کردن return، نیمی از محتوای سایت را نابود کرد

زمستان ۱۳۹۵، یکی از اولین پروژه‌های جدی‌ام را تحویل می‌دادم؛ یک سایت محتوایی با حدود ۳۰۰ نوشته. مدیر سایت زنگ زد که «نیمی از نوشته‌های سایت خالی شده‌اند، فقط عنوان و تصویر می‌بینیم». رفتم سراغ کد و بعد از یک ساعت بررسی، ریشه را پیدا کردم: من یک فیلتر روی the_content نوشته بودم که در حالت خاصی، به‌جای return مقدار، با echo یک پیام چاپ می‌کرد. برای همه نوشته‌هایی که شرط فعال می‌شد، callback مقدار null برمی‌گرداند و وردپرس محتوا را خالی می‌کرد. رفع آن، یک خط تغییر بود؛ پیدا کردنش، یک ساعت. آن روز یک قاعده بنیادین در ذهنم حک شد: در فیلتر، همیشه return کنید؛ هیچ‌وقت echo. بدون استثنا. این مقاله، همان قاعده و پروتکل کامل استفاده از add_filter است که در پروژه‌های واقعی به‌کار می‌برم. اگر با مفاهیم پایه آشنا نیستید، پیش از ادامه هوک‌های وردپرس چیست، تفاوت اکشن و فیلتر در وردپرس و نحوه استفاده از add_action را بخوانید. مکمل این مقاله مهم‌ترین فیلتر هوک‌های وردپرس، ساخت فیلتر سفارشی، Priority در هوک‌ها و راهنمای حرفه‌ای کار با هوک‌ها است.

add_filter دقیقاً چه کاری انجام می‌دهد؟

add_filter تابعی است که به وردپرس می‌گوید «هر زمان داده‌ای از فیلتر X عبور کرد، تابع Y من را صدا بزن تا آن داده را تغییر دهم». تفاوت بنیادین با add_action: در فیلتر، داده‌ای پاس داده می‌شود و شما موظف هستید مقدار جدید را return کنید. اگر مقدار را برنگردانید، داده نابود می‌شود. سه نکته بنیادین: یک — add_filter فقط ثبت می‌کند؛ اجرای واقعی زمانی است که هسته یا افزونه‌ای دیگر با apply_filters از آن نقطه عبور کند. دو — callback فیلتر همیشه مقدار را می‌گیرد و همیشه مقدار را برمی‌گرداند. سه — اگر فیلتر در آن درخواست اجرا نشود، callback شما هم اجرا نمی‌شود؛ این نکته در AJAX و REST حیاتی است. توضیح کامل مکانیزم در هوک‌های وردپرس چیست، ساختار هسته وردپرس و ساخت قابلیت اختصاصی با هوک‌ها آمده است.

add_filter، مثل ایستگاه بازرسی است؛ داده از آن عبور می‌کند، بازرس آن را تغییر می‌دهد و تحویل می‌دهد. اگر بازرس، محموله را تحویل ندهد، گیرنده چیزی دریافت نمی‌کند.

نحو add_filter و چهار پارامتر آن

تابع add_filter چهار پارامتر دارد؛ دوتای اول الزامی، دوتای دوم اختیاری:

add_filter( $hook_name, $callback, $priority = 10, $accepted_args = 1 );

هر پارامتر را با یک مثال واقعی باز می‌کنم:

پارامتر اول — $hook_name

نام رشته‌ای فیلتر. مثلاً the_content، the_title، excerpt_length، wp_insert_post_data. یک اشتباه تایپی در این نام، callback شما را بی‌اثر می‌کند بدون آن‌که هیچ خطایی صادر شود. راهنمای فهرست فیلترها در مهم‌ترین فیلتر هوک‌های وردپرس و مهم‌ترین اکشن هوک‌های وردپرس آمده است.

پارامتر دوم — $callback

تابعی که داده را تغییر می‌دهد. چهار شکل دارد:

// شکل اول: نام تابع سراسری
add_filter( 'the_content', 'myplugin_append_signature' );

// شکل دوم: متد استاتیک کلاس
add_filter( 'the_content', array( 'My_Plugin', 'modify_content' ) );

// شکل سوم: متد شیء
$obj = new My_Plugin();
add_filter( 'the_content', array( $obj, 'modify_content' ) );

// شکل چهارم (توصیه نمی‌شود): تابع ناشناس
add_filter( 'the_content', function( $content ) {
    return $content;
} );

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

پارامتر سوم — $priority

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

// اجرا قبل از افزونه سئو (که معمولاً priority 10 دارد)
add_filter( 'the_content', 'myplugin_early_meta', 5 );

// اجرا بعد از افزونه‌های دیگر (مثلاً افزودن امضا پس از افزونه اشتراک‌گذاری)
add_filter( 'the_content', 'myplugin_append_signature', 20 );

// اجرا در آخرین لحظه (override همه)
add_filter( 'the_content', 'myplugin_final_word', 999 );

راهنمای کامل priority در Priority در هوک‌ها، کنترل ترتیب اجرای هوک‌ها و راهنمای حرفه‌ای کار با هوک‌ها. یک قاعده: هر priority غیرپیش‌فرض را با یک کامنت مستند کنید؛ سه ماه بعد، خودتان هم نمی‌دانید چرا ۲۰ گذاشته‌اید.

پارامتر چهارم — $accepted_args

تعداد پارامترهایی که callback شما دریافت می‌کند. پیش‌فرض ۱. اگر فیلتر دو یا سه پارامتر پاس می‌دهد و شما این پارامتر را ندهید، callback فقط پارامتر اول را می‌بیند:

// نامناسب - فقط $title را می‌گیرد
add_filter( 'the_title', 'myplugin_modify_title' );

// مناسب - دو پارامتر را می‌گیرد
add_filter( 'the_title', 'myplugin_modify_title', 10, 2 );

function myplugin_modify_title( $title, $post_id ) {
    return $title;
}

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

قاعده طلایی: همیشه return کنید

پرکاربردترین باگ در فیلترها، فراموش‌کردن return است. سه الگوی درست و غلط:

// غلط - با echo چاپ می‌کند
add_filter( 'the_content', 'myplugin_add_note' );

function myplugin_add_note( $content ) {
    echo '<p>یادداشت پایانی</p>';
}

// درست - مقدار را return می‌کند
add_filter( 'the_content', 'myplugin_add_note' );

function myplugin_add_note( $content ) {
    return $content . '<p>یادداشت پایانی</p>';
}

حتی اگر شرط شما برقرار نباشد، باید مقدار اصلی را برنگردانید:

// نامناسب - اگر شرط برقرار نباشد، null برمی‌گردد
function myplugin_modify_content( $content ) {
    if ( is_singular( 'post' ) ) {
        return $content . '<p>متن اضافه</p>';
    }
    // return فراموش شده
}

// مناسب - همیشه return دارد
function myplugin_modify_content( $content ) {
    if ( ! is_singular( 'post' ) ) {
        return $content;
    }
    return $content . '<p>متن اضافه</p>';
}

راهنمای امنیت و پاک‌سازی داده در PHP امن در وردپرس، پاک‌سازی داده‌ها، اعتبارسنجی داده‌ها و توابع امنیت و پاک‌سازی آمده است.

فیلتر بدون return، مثل چراغ‌قوه‌ای بدون لامپ است؛ باتری کار می‌کند ولی نور نمی‌دهد.

مثال‌های کاربردی از پروژه‌های واقعی

مثال اول — افزودن متن به انتهای محتوا:

add_filter( 'the_content', 'myplugin_append_source', 12 );

function myplugin_append_source( $content ) {
    if ( ! is_singular( 'post' ) || is_admin() ) {
        return $content;
    }
    $source = get_post_meta( get_the_ID(), '_source', true );
    if ( ! empty( $source ) ) {
        $content .= sprintf(
            '<p class="source-note">منبع: %s</p>',
            esc_html( $source )
        );
    }
    return $content;
}

راهنمای کامل در هوک‌های محتوای نوشته، توابع داده‌های نوشته، کار با متاباکس‌ها، توابع متادیتا و سئوی درون‌صفحه.

مثال دوم — کوتاه‌کردن خلاصه:

add_filter( 'excerpt_length', 'myplugin_short_excerpt' );

function myplugin_short_excerpt( $length ) {
    return 25;
}

مثال سوم — تغییر عنوان نوشته:

add_filter( 'the_title', 'myplugin_modify_title', 20, 2 );

function myplugin_modify_title( $title, $post_id ) {
    if ( ! is_singular( 'post' ) ) {
        return $title;
    }
    return $title . ' | سایت من';
}

مثال چهارم — تغییر مقدار پیش‌فرض در Settings API:

add_filter( 'default_option_myplugin_settings', 'myplugin_defaults' );

function myplugin_defaults( $default ) {
    return array(
        'api_key' => '',
        'debug'   => false,
    );
}

راهنمای کامل در ساخت صفحه تنظیمات اختصاصی، کار با Options API، توابع گزینه‌های سایت، ساخت منوی مدیریتی وردپرس و Customizer وردپرس.

priority در عمل: سه سناریوی واقعی

سناریو اول — افزودن محتوا پیش از سایر افزونه‌ها: اگر می‌خواهید محتوای شما قبل از افزونه اشتراک‌گذاری یا تبلیغات اضافه شود، priority کمتر از ۱۰:

add_filter( 'the_content', 'myplugin_add_disclaimer', 5 );

سناریو دوم — افزودن امضا بعد از همه: اگر می‌خواهید امضای شما آخرین بخش محتوا باشد، priority بالاتر:

add_filter( 'the_content', 'myplugin_append_signature', 999 );

سناریو سوم — بازنویسی خروجی افزونه دیگر: اگر می‌خواهید خروجی افزونه‌ای را تغییر دهید، callback شما باید بعد از آن اجرا شود:

add_filter( 'the_content', 'myplugin_override_output', 20 );

راهنمای کامل در Priority در هوک‌ها، کنترل ترتیب اجرای هوک‌ها، حذف فیلتر هوک، حذف اکشن هوک و راهنمای حرفه‌ای کار با هوک‌ها آمده است. یک تجربه میدانی: در پروژه‌ای، افزونه اشتراک‌گذاری priority 15 داشت و امضای ما priority 10؛ در نتیجه امضا قبل از دکمه‌های اشتراک‌گذاری می‌آمد و چیدمان را به‌هم می‌زد. تغییر priority به 20، مشکل را حل کرد.

الگوی کلاس‌محور: Registry Pattern

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

class My_Plugin_Filters {
    public static function init() {
        add_filter( 'the_content', array( __CLASS__, 'modify_content' ), 12 );
        add_filter( 'the_title', array( __CLASS__, 'modify_title' ), 20, 2 );
        add_filter( 'excerpt_length', array( __CLASS__, 'short_excerpt' ) );
        add_filter( 'wp_insert_post_data', array( __CLASS__, 'sanitize_post_data' ), 10, 2 );
    }

    public static function modify_content( $content ) {
        return $content;
    }

    public static function modify_title( $title, $post_id ) {
        return $title;
    }

    public static function short_excerpt( $length ) {
        return 25;
    }

    public static function sanitize_post_data( $data, $postarr ) {
        return $data;
    }
}
My_Plugin_Filters::init();

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

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

حذف add_filter با remove_filter

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

remove_filter( 'the_content', 'myplugin_append_signature', 12 );

// برای متدهای کلاس استاتیک
remove_filter( 'the_content', array( 'My_Plugin', 'method_name' ), 10 );

// برای متدهای شیء
$obj = My_Plugin::get_instance();
remove_filter( 'the_content', array( $obj, 'method_name' ), 10 );

سه نکته حیاتی: یک — priority باید مطابق باشد. اگر فیلتر با priority 12 ثبت شده و شما 10 بدهید، حذف نمی‌شود. دو — حذف باید بعد از ثبت انجام شود. اگر افزونه‌ای دیرتر از شما بار شود، کد حذف شما اثری ندارد. سه — callbackهای closure قابل حذف نیستند. راهنمای کامل در حذف فیلتر هوک، حذف اکشن هوک، قالب چایلد چیست، توسعه با چایلد تم و استفاده درست از هوک‌ها آمده است.

یک تجربه میدانی: در پروژه‌ای، افزونه‌ای متن تبلیغاتی به انتهای هر نوشته اضافه می‌کرد. سازنده افزونه راهی برای غیرفعال‌کردن آن نگذاشته بود. راه‌حل: در چایلد تم، با remove_filter و priority درست، متن تبلیغاتی حذف شد.

الگوی پیشرفته: روش Object-Oriented

در افزونه‌های حرفه‌ای، از یک کلاس با متدهای نمونه استفاده کنید:

class My_Plugin_Content_Filters {
    public function __construct() {
        add_filter( 'the_content', array( $this, 'append_share_buttons' ), 15 );
        add_filter( 'the_title', array( $this, 'decorate_title' ), 10, 2 );
        add_filter( 'excerpt_more', array( $this, 'custom_excerpt_more' ) );
    }

    public function append_share_buttons( $content ) {
        if ( ! is_singular( 'post' ) ) {
            return $content;
        }
        $buttons = '<div class="share-buttons">...</div>';
        return $content . $buttons;
    }

    public function decorate_title( $title, $post_id ) {
        return $title;
    }

    public function custom_excerpt_more( $more ) {
        return '…';
    }
}

new My_Plugin_Content_Filters();

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

add_filter در قالب و افزونه: تفاوت‌های ظریف

سه قاعده عملی:

add_filter در ووکامرس: نکات ویژه

در فروشگاه‌های ووکامرسی، add_filter ابزار اصلی سفارشی‌سازی رفتار محصولات و سبد است:

// تغییر قیمت محصول
add_filter( 'woocommerce_product_get_price', 'myshop_apply_discount', 10, 2 );

function myshop_apply_discount( $price, $product ) {
    if ( ! is_user_logged_in() ) {
        return $price;
    }
    $tier = get_user_meta( get_current_user_id(), '_membership_tier', true );
    if ( 'gold' === $tier ) {
        $price = (float) $price * 0.9;
    }
    return $price;
}

// تغییر متن دکمه افزودن به سبد
add_filter( 'woocommerce_product_add_to_cart_text', 'myshop_custom_cart_text', 10, 2 );

function myshop_custom_cart_text( $text, $product ) {
    return 'همین حالا بخرید';
}

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

add_filter در مسیر پردازش داده: wp_insert_post_data

یکی از پرکاربردترین فیلترهای پیشرفته، wp_insert_post_data است که پیش از ذخیره نوشته در دیتابیس اجرا می‌شود:

add_filter( 'wp_insert_post_data', 'myplugin_trim_title', 10, 2 );

function myplugin_trim_title( $data, $postarr ) {
    if ( 'post' !== $data['post_type'] ) {
        return $data;
    }
    $data['post_title'] = trim( $data['post_title'] );
    return $data;
}

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

دیباگ add_filter: ابزارها

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

  • Query Monitor: لیست تمام فیلترها با ترتیب و priority و callback. راهنمای کامل در توابع دیباگ وردپرس.
  • error_log در callback: بررسی اینکه callback اجرا می‌شود یا نه. راهنما در دیباگ کد سفارشی وردپرس.
  • global $wp_filter: برای دیدن تمام callbackهای ثبت‌شده روی یک فیلتر. راهنما در دیباگ اکشن و فیلتر.
  • تست با priority بالا: اگر callback شما در انتهای زنجیره اجرا می‌شود، از priority بالا استفاده کنید.

یک تجربه میدانی: در پروژه‌ای، callback ما در فیلتر the_content کار نمی‌کرد. با Query Monitor کشف کردیم که افزونه‌ای دیگر با priority 5 مقدار را جایگزین می‌کند و فیلتر ما با priority 10 روی مقدار جدید اجرا می‌شود، ولی به دلیل شرط داخل callback، تغییر اعمال نمی‌شود. تنظیم priority، مشکل را حل کرد. راهنما در تست و دیباگ پروژه‌های وردپرس، بهترین روش تست وردپرس و خطای Deprecated در PHP.

اشتباهات رایج در استفاده از add_filter

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

امنیت در add_filter: پنج قاعده طلایی

هر callback که با add_filter ثبت می‌شود، یک نقطه ورود بالقوه است. پنج قاعده امنیتی الزامی:

  1. Escape خروجی پس از فیلتر: اگر مقدار فیلترشده در HTML نمایش داده می‌شود، با esc_html، esc_url یا esc_attr خروجی دهید. راهنما در PHP امن در وردپرس.
  2. اعتبارسنجی نوع بازگشتی: اگر فیلتر عددی است، مقدار بازگشتی را با (int) یا (float) محدود کنید. راهنما در اعتبارسنجی داده‌ها.
  3. پاک‌سازی ورودی در فیلترهای ذخیره: sanitize_text_field، absint، esc_url_raw. راهنما در پاک‌سازی داده‌ها.
  4. بررسی دسترسی در callbackهای حساس: current_user_can پیش از هر عملیات. راهنما در نقش و دسترسی.
  5. نانس در فرم‌ها و AJAX: wp_verify_nonce و check_ajax_referer. راهنما در نانس وردپرس و پیاده‌سازی نانس در فرم‌ها.

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

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

جمع‌بندی

نحوه استفاده از add_filter در وردپرس، در چهار اصل خلاصه می‌شود: انتخاب فیلتر درست برای نقطه تبدیل داده، callback نام‌دار با پیشوند یکتا برای جلوگیری از تعارض، priority با دلیل و مستندسازی برای ترتیب اجرا، و قاعده طلایی return برای حفظ داده. سه اصل را در پایان تاکید می‌کنم: اول، در فیلتر همیشه return کنید؛ هیچ‌وقت echo. دوم، callback را همیشه نام‌دار بنویسید تا امکان حذف توسط افزونه‌های دیگر باقی بماند. سوم، پس از فیلتر، مقدار را اعتبارسنجی و escape کنید تا داده آلوده به خروجی نرسد.

اگر امروز یک کار در این مسیر انجام می‌دهید: در آخرین افزونه یا قالب خود، فهرست تمام فراخوانی‌های add_filter را بنویسید و سه چیز را بازبینی کنید — وجود return، priority، و تعداد پارامترهای accepted_args. همان یک بازبینی کوچک، در آپدیت بعدی نجات‌دهنده است. اگر تجربه‌ای از یک add_filter دارید که پروژه‌ای را نجات داد یا باگی را حل کرد، در دیدگاه‌ها بنویسید — همان گزارش‌های واقعی، این راهنما را برای توسعه‌دهنده بعدی دقیق‌تر می‌کند. 🔧