شبی که یک فیلتر، محتوای سه سال سایت را نجات داد

پاییز ۱۳۹۷، یک سایت خبری با ده سال آرشیو را برای بهینه‌سازی تحویل گرفتم. مدیر سایت شکایت داشت که «همه نوشته‌های قدیمی، لینک‌های خارجی با rel=nofollow را از دست داده‌اند و گوگل به سایت ما اعتماد نمی‌کند». رفتم سراغ کد و بعد از نیم روز بررسی، کشف کردم افزونه‌ای که زمانی برای مدیریت لینک‌های خارجی نصب شده بود، در یک آپدیت، فیلترش را از دست داده و از آن روز، همه لینک‌های خارجی در بیش از سه هزار نوشته، بدون rel=nofollow رندر می‌شدند. راه‌حل، سه خط کد بود: یک فیلتر روی the_content که هر لینک خارجی را می‌گرفت و rel=nofollow به آن اضافه می‌کرد. آن سه خط، بدون بازنویسی یک نوشته، اعتماد گوگل را به سایت برگرداند. آن شب یک درس بنیادین گرفتم: فیلتر هوک‌ها، چاقوی جراحی وردپرس هستند؛ اگر درست دست بگیرید، بدون آسیب به محتوا، می‌توانید در هر نوشته‌ای دخالت کنید.

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

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

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

فیلتر هوک، مانند صافی قهوه است؛ داده از آن عبور می‌کند، ولی اگر صافی را نابود کنید، همه چیز از بین می‌رود.

دسته‌بندی فیلتر هوک‌ها بر اساس داده

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

  1. فیلترهای محتوا: the_content، the_excerpt، the_title، the_editor_content.
  2. فیلترهای URL: home_url، site_url، get_permalink، admin_url.
  3. فیلترهای کوئری: pre_get_posts، posts_where، posts_orderby.
  4. فیلترهای ذخیره: wp_insert_post_data، sanitize_option_*، pre_update_option_*.
  5. فیلترهای کاربر: user_contactmethods، editable_roles، user_has_cap.
  6. فیلترهای پیشخوان: admin_footer_text، admin_body_class، plugin_action_links.
  7. فیلترهای WooCommerce: woocommerce_product_get_price، woocommerce_cart_item_name، woocommerce_email_subject_*.

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

فیلترهای محتوا: the_content و مشتقاتش

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

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

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

سه نکته حیاتی: یک — همیشه return کنید. بدون return، محتوا نابود می‌شود. دو — بررسی وضعیت صفحه. اگر فیلتر فقط برای نوشته‌های تکی است، is_singular را چک کنید؛ در غیر این صورت، فیلتر در آرشیو، RSS و API هم اجرا می‌شود. سه — priority هوشمند. گاهی فیلتر شما باید قبل یا بعد از افزونه‌ای دیگر اجرا شود. راهنمای کامل در هوک‌های محتوای نوشته، توابع داده‌های نوشته، Priority در هوک‌ها و کنترل ترتیب اجرای هوک‌ها.

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

  • the_title: تغییر عنوان نوشته، مثلاً برای افزودن پیشوند یا پسوند. راهنما در توابع داده نوشته.
  • the_excerpt: تغییر خلاصه نوشته. اگر قالب شما خلاصه اتوماتیک دارد، این فیلتر بهترین نقطه دخالت است.
  • excerpt_length: تعیین طول خلاصه خودکار. پیش‌فرض ۵۵ کلمه است.

نمونه کاربردی برای کوتاه‌کردن خلاصه:

add_filter( 'excerpt_length', function( $length ) {
    return 25;
} );

فیلترهای URL: home_url، site_url و get_permalink

این دسته، در پروژه‌های مهاجرت و چند‌زبانه حیاتی است:

add_filter( 'home_url', 'myplugin_adjust_home_url', 10, 2 );

function myplugin_adjust_home_url( $url, $path ) {
    if ( is_admin() ) {
        return $url;
    }
    // تغییر مشروط URL برای محیط خاص
    return $url;
}

سه نکته: یک — پارامتر دوم $path: مسیر درخواستی را می‌دهد. دو — احتیاط در محیط توسعه: تغییر home_url یا site_url در محیط توسعه می‌تواند پیشخوان را از کار بیندازد. سه — همیشه خروجی را escape کنید. راهنمای کامل در توابع لینک و URL، توابع ریدایرکت، ساختار URL و سئو، راهنمای انتخاب هاست و بررسی وضعیت ورود.

فیلترهای کوئری: pre_get_posts، posts_where و posts_orderby

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

add_action( 'pre_get_posts', 'myplugin_include_cpt_in_search' );

function myplugin_include_cpt_in_search( $query ) {
    if ( is_admin() || ! $query->is_main_query() ) {
        return;
    }
    if ( $query->is_search() ) {
        $query->set( 'post_type', array( 'post', 'page', 'portfolio' ) );
    }
}

نکته مهم: pre_get_posts یک اکشن است، نه فیلتر، ولی از نظر ماهیت کار، دقیقاً در دسته «تغییر داده کوئری» جای می‌گیرد. دو بررسی الزامی: is_admin() و is_main_query(). بدون این دو، تغییرات شما ممکن است روی پیشخوان یا حلقه‌های جانبی اثر بگذارد و نتایج غیرمنتظره بدهد. راهنمای کامل در کدنویسی کوئری سفارشی، توابع کوئری سفارشی، بهینه‌سازی کوئری‌ها، بهینه‌سازی کوئری‌های MySQL، تأثیر دیتابیس بر سرعت سایت و کار با CPT در وردپرس.

سه فیلتر کوئری دیگر که در پروژه‌ها به‌کار می‌برم:

  • posts_where: افزودن شرط WHERE سفارشی به کوئری.
  • posts_orderby: سفارشی‌سازی ORDER BY. راهنمای کاربردی در توابع کوئری سفارشی.
  • posts_clauses: تغییر کل بخش‌های کوئری. قدرتمند ولی خطرناک.

فیلترهای ذخیره: wp_insert_post_data و sanitize_option_*

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

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;
}

نکته: همیشه بررسی کنید نوع‌نوشته درست است تا فیلتر روی انواع دیگر اثر نگذارد. راهنمای کامل در توابع ایجاد و حذف نوشته، کار با متاباکس‌ها، توابع متادیتا، کار با Options API، توابع گزینه‌های سایت و کار با User Meta.

فیلترهای کاربر: user_contactmethods و user_has_cap

در سایت‌های عضویتی، این دسته بسیار کاربرد دارد:

یک — user_contactmethods: افزودن فیلد تماس سفارشی به پروفایل کاربر:

add_filter( 'user_contactmethods', 'myplugin_add_contact_fields' );

function myplugin_add_contact_fields( $methods ) {
    $methods['whatsapp'] = 'واتس‌اپ';
    $methods['linkedin'] = 'لینکدین';
    return $methods;
}

دو — user_has_cap: کنترل دقیق دسترسی کاربران:

add_filter( 'user_has_cap', 'myplugin_restrict_access', 10, 4 );

function myplugin_restrict_access( $allcaps, $caps, $args, $user ) {
    if ( in_array( 'access_premium', $caps, true ) ) {
        $expire = get_user_meta( $user->ID, '_premium_expire', true );
        if ( $expire && strtotime( $expire ) < time() ) {
            $allcaps['access_premium'] = false;
        }
    }
    return $allcaps;
}

راهنمای کامل در توابع کاربران، توابع داده‌های کاربر، توابع نقش و دسترسی، هوک‌های مدیریت کاربران و هوک‌های ورود و ثبت‌نام.

فیلتر کاربر، مثل نگهبانِ در است؛ اگر درست تنظیمش کنید، بدون تغییر کد اصلی، دسترسی‌ها را کنترل می‌کند.

فیلترهای پیشخوان: admin_footer_text و plugin_action_links

در پروژه‌های مشتری‌محور، این فیلترها تجربه مدیریت را حرفه‌ای‌تر می‌کنند:

یک — admin_footer_text: تغییر متن فوتر پیشخوان:

add_filter( 'admin_footer_text', function( $text ) {
    return 'ساخته‌شده با ❤️ در تیم ما';‌
} );

دو — plugin_action_links_{$plugin}: افزودن لینک سریع به صفحه تنظیمات افزونه در لیست افزونه‌ها:

add_filter( 'plugin_action_links_my-plugin/my-plugin.php', 'myplugin_add_settings_link' );

function myplugin_add_settings_link( $links ) {
    $settings = sprintf(
        '<a href="%s">تنظیمات</a>',
        esc_url( admin_url( 'options-general.php?page=my-plugin' ) )
    );
    array_unshift( $links, $settings );
    return $links;
}

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

فیلترهای WooCommerce: woocommerce_product_get_price و مشابهش

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

یک — woocommerce_product_get_price: تغییر قیمت محصول در لحظه محاسبه:

add_filter( 'woocommerce_product_get_price', 'myplugin_apply_user_discount', 10, 2 );

function myplugin_apply_user_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 = $price * 0.9;
    }
    return $price;
}

دو — woocommerce_cart_item_name: تغییر نام محصول در سبد خرید.

سه — woocommerce_email_subject_new_order: تغییر عنوان ایمیل سفارش جدید.

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

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

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

// در افزونه شما
$final_price = apply_filters( 'myplugin_final_price', $base_price, $user_id );

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

function other_plugin_apply_tax( $price, $user_id ) {
    return $price * 1.09;
}

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

حذف فیلترها: remove_filter

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

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

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

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

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

اولویت و تعداد پارامترها در فیلترها

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

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

نمونه‌ای که در پروژه‌های واقعی زیاد لازم می‌شود: تغییر عنوان نوشته پس از افزونه سئو:

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

function mytheme_append_year( $title, $post_id ) {
    if ( is_singular( 'post' ) ) {
        $year = get_the_date( 'Y', $post_id );
        $title .= ' (' . $year . ')';
    }
    return $title;
}

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

دیباگ فیلترها: ابزارها

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

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

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

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

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

  1. پاک‌سازی ورودی در فیلترهای ذخیره: sanitize_* قبل از هر پردازش. راهنما در پاک‌سازی داده‌ها.
  2. Escape خروجی در فیلترهای نمایش: esc_html، esc_url، esc_attr. راهنما در PHP امن در وردپرس.
  3. اعتبارسنجی داده در فیلترهای ذخیره: absint، sanitize_email، wp_kses_post بسته به نوع. راهنما در اعتبارسنجی داده‌ها.
  4. بررسی دسترسی در فیلترهای حساس: current_user_can پیش از هر عملیات. راهنما در نقش و دسترسی.
  5. نانس در فرم‌ها و AJAX: wp_verify_nonce و check_ajax_referer. راهنما در نانس وردپرس و پیاده‌سازی نانس در فرم‌ها.

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

اشتباهات رایج در فیلترها

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

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

الگوی حرفه‌ای: Registry Pattern برای فیلترها

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

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__, 'excerpt_length' ) );
        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 excerpt_length( $length ) {
        return 25;
    }

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

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

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

فیلترهای پیشرفته در پروژه‌های واقعی

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

یک — upload_mimes: محدودسازی یا گسترش انواع فایل مجاز برای آپلود. راهنمای کامل در توابع کار با فایل و توابع مدیریت رسانه‌ها.

دو — wp_handle_upload_prefilter: پردازش فایل پیش از ذخیره. نمونه:

add_filter( 'wp_handle_upload_prefilter', 'myplugin_rename_upload' );

function myplugin_rename_upload( $file ) {
    $file['name'] = 'custom-' . time() . '-' . sanitize_file_name( $file['name'] );
    return $file;
}

سه — wp_mail: تغییر پارامترهای ایمیل پیش از ارسال. راهنما در توابع HTTP وردپرس.

فیلترهای ترجمه و بین‌المللی‌سازی

در سایت‌های چندزبانه، فیلترها نقش کلیدی دارند:

  • locale — تغییر زبان جاری.
  • gettext — تغییر رشته‌های ترجمه‌شده.
  • gettext_with_context — تغییر رشته‌های ترجمه‌شده با context.

راهنمای کامل در آماده‌سازی قالب برای فارسی، تفاوت قالب فارسی و انگلیسی، قالب ریسپانسیو چیست، Core Web Vitals و هدرهای امنیتی HTTP.

جمع‌بندی

مهم‌ترین فیلتر هوک‌های وردپرس، در هفت دسته خلاصه می‌شوند: محتوا (the_content، the_title، the_excerpt)، URL (home_url، site_url)، کوئری (pre_get_posts، posts_where)، ذخیره (wp_insert_post_data، sanitize_option_*)، کاربر (user_contactmethods، user_has_cap)، پیشخوان (admin_footer_text، plugin_action_links) و WooCommerce (woocommerce_product_get_price و مشابهش). سه اصل را در پایان تاکید می‌کنم: اول، فیلتر را به‌عنوان صافی ببینید، نه به‌عنوان دکمه. دوم، در callback فیلتر، همیشه مقدار را return کنید؛ هیچ‌وقت با echo چاپ نکنید. سوم، امنیت را در هر فیلتر جدی بگیرید — پاک‌سازی ورودی، escape خروجی و بررسی دسترسی.

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