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

تابع plugin_basename وردپرس یکی از پرکاربردترین توابع ساختاری برای دریافت نام پایه افزونه است. این تابع امکان ساخت لینک تنظیمات، فیلترهای plugin_action_links و شناسه‌های هوک اختصاصی را فراهم می‌کند و پایه ساختاردهی افزونه‌های حرفه‌ای محسوب می‌شود. در این راهنما ساختار کامل، پارامترها، نمونه‌های واقعی، اشتباهات رایج و نکات امنیتی این تابع بررسی می‌شود. همچنین تفاوت آن با plugin_dir_path و plugin_dir_url توضیح داده می‌شود. در پایان پرسش‌های پرتکرار و نگاه فنی عمیق به این تابع مرور خواهد شد.

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

چرا plugin_basename اهمیت دارد

هر افزونه وردپرس یک فایل اصلی دارد که در فهرست افزونه‌ها نمایش داده می‌شود. مسیر این فایل به‌صورت نسبی از پوشه wp-content/plugins شناخته می‌شود. این مسیر نسبی، همان plugin_basename است.

تابع plugin_basename() این مسیر نسبی را برمی‌گرداند و به‌عنوان شناسه یکتای افزونه در اکوسیستم وردپرس استفاده می‌شود. این شناسه در چند سناریوی مهم کاربرد دارد:

  • ساخت فیلتر plugin_action_links_{basename} برای افزودن لینک تنظیمات
  • ساخت هوک اختصاصی برای auto-update یا اعلان‌ها
  • ثبت متادیتای افزونه در مخزن وردپرس
  • تشخیص پوشه افزونه در زمان بارگذاری فایل ترجمه

برای درک کامل جایگاه این تابع در کنار توابع مرتبط، مطالب تابع plugin_dir_path و تابع plugin_dir_url را مطالعه کنید.

ساختار و امضای تابع plugin_basename

امضای این تابع به شکل زیر است:

plugin_basename( string $file ): string

پارامتر ورودی یک مسیر فایل است که معمولاً با __FILE__ پاس داده می‌شود. خروجی یک رشته است که مسیر نسبی فایل از پوشه wp-content/plugins را نشان می‌دهد.

مثال عملی:

// فرض کنید فایل اصلی افزونه در این مسیر است:
// /var/www/html/wp-content/plugins/my-plugin/my-plugin.php

$basename = plugin_basename( __FILE__ );
// نتیجه: my-plugin/my-plugin.php

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

پارامترها و کاربرد __FILE__

این تابع فقط یک پارامتر دارد که معمولاً با __FILE__ پاس داده می‌شود. تابع plugin_basename درون خود این مسیر را نسبت به مسیر پوشه افزونه‌ها محاسبه می‌کند.

define( 'MYPLUGIN_BASENAME', plugin_basename( __FILE__ ) );

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

پیاده‌سازی داخلی

پیاده‌سازی این تابع نسبت به توابع دیگر کمی پیچیده‌تر است چون باید مسیر را نسبت به پوشه افزونه‌ها محاسبه کند:

function plugin_basename( $file ) {
    global $wp_plugin_paths;

    if ( ! isset( $wp_plugin_paths ) ) {
        $wp_plugin_paths = array();
        foreach ( wp_get_active_and_valid_plugins() as $plugin ) {
            $wp_plugin_paths[ wp_normalize_path( realpath( dirname( $plugin ) ) ) ] = wp_normalize_path( dirname( $plugin ) );
        }
    }

    foreach ( $wp_plugin_paths as $dir => $realdir ) {
        if ( strpos( $file, $dir ) !== false ) {
            $file = $realdir . substr( $file, strlen( $dir ) );
            break;
        }
    }

    $file = wp_normalize_path( $file );
    return trim( substr( $file, strlen( WP_PLUGIN_DIR ) + 1 ), '/' );
}

این پیچیدگی برای پشتیبانی از symlink و پیکربندی‌های سفارشی سرور ضروری است.

یکی از پرکاربردترین سناریوهای استفاده از این تابع، افزودن لینک «تنظیمات» در فهرست افزونه‌هاست:

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

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

افزودن لینک‌های دیگر

می‌توانید لینک‌های دیگری مثل «مستندات»، «پشتیبانی» یا «تنظیمات پیشرفته» نیز اضافه کنید:

add_filter(
    'plugin_action_links_' . plugin_basename( __FILE__ ),
    function ( $links ) {
        $docs_link = sprintf(
            '<a href="%s" target="_blank" rel="noopener">%s</a>',
            esc_url( 'https://example.com/docs' ),
            esc_html__( 'مستندات', 'my-plugin' )
        );
        array_push( $links, $docs_link );
        return $links;
    }
);

افزودن لینک در فهرست شبکه Multisite

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

add_filter(
    'network_admin_plugin_action_links_' . plugin_basename( __FILE__ ),
    'myplugin_network_action_links'
);

برای مطالعه کامل Multisite، مطلب مدیریت Multisite وردپرس را ببینید.

نمونه‌های عملی در پروژه واقعی

تعریف constant basename در فایل اصلی افزونه

// my-plugin.php
if ( ! defined( 'MYPLUGIN_BASENAME' ) ) {
    define( 'MYPLUGIN_BASENAME', plugin_basename( __FILE__ ) );
}

این الگو در تمام افزونه‌های حرفه‌ای رایج است.

بارگذاری فایل ترجمه

add_action( 'plugins_loaded', function () {
    load_plugin_textdomain(
        'my-plugin',
        false,
        dirname( plugin_basename( __FILE__ ) ) . '/languages'
    );
} );

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

هوک اختصاصی برای بروزرسانی افزونه

add_action(
    'in_plugin_update_message-' . plugin_basename( __FILE__ ),
    function ( $plugin_data, $response ) {
        if ( version_compare( $plugin_data['Version'], $response->new_version, '<' ) ) {
            echo '<div class="update-message">' . esc_html__( 'نسخه جدید موجود است. لطفاً قبل از بروزرسانی از سایت بکاپ بگیرید.', 'my-plugin' ) . '</div>';
        }
    },
    10,
    2
);

این الگو به کاربر هشدار می‌دهد که قبل از بروزرسانی اقدامات احتیاطی انجام دهد. مطلب تابع register_activation_hook برای مدیریت چرخه عمر افزونه مفید است.

شناسایی افزونه در لاگ‌ها

error_log( sprintf( '[%s] خطای رخ داده در پردازش سفارش', MYPLUGIN_BASENAME ) );

در لاگ‌های حرفه‌ای، ذکر basename افزونه به ردیابی سریع‌تر کمک می‌کند.

ساخت شناسه یکتا برای فیلتر

add_filter(
    'myplugin_data_' . MYPLUGIN_BASENAME,
    'myplugin_filter_data'
);

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

نمایش اطلاعات افزونه در پنل مدیریت

printf(
    '<p>%s: <code>%s</code></p>',
    esc_html__( 'شناسه افزونه', 'my-plugin' ),
    esc_html( MYPLUGIN_BASENAME )
);

استفاده از esc_html برای escape کردن خروجی ضروری است.

ترکیب با plugin_dir_path و plugin_dir_url

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

define( 'MYPLUGIN_DIR' ) or define( 'MYPLUGIN_DIR', plugin_dir_path( __FILE__ ) );
define( 'MYPLUGIN_URL' ) or define( 'MYPLUGIN_URL', plugin_dir_url( __FILE__ ) );
define( 'MYPLUGIN_BASENAME' ) or define( 'MYPLUGIN_BASENAME', plugin_basename( __FILE__ ) );

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

استفاده در Composer Autoload

if ( file_exists( MYPLUGIN_DIR . 'vendor/autoload.php' ) ) {
    require_once MYPLUGIN_DIR . 'vendor/autoload.php';
}

برای مطالعه بیشتر درباره Composer در PHP، مطلب PHP Composer Deep Dive راهنماست.

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

نبود __FILE__ در زمان استفاده

اگر به‌جای __FILE__ یک رشته مسیر دستی پاس دهید، basename در محیط‌های مختلف متفاوت خواهد بود. همیشه از __FILE__ استفاده کنید یا constant سراسری تعریف کنید.

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

اگر در فایل‌های مختلف افزونه به‌طور جداگانه plugin_basename( __FILE__ ) فراخوانی کنید، مقدار خروجی در فایل‌های داخل پوشه‌های فرعی متفاوت خواهد بود:

// /plugins/my-plugin/my-plugin.php
plugin_basename( __FILE__ );
// نتیجه: my-plugin/my-plugin.php

// /plugins/my-plugin/includes/class-loader.php
plugin_basename( __FILE__ );
// نتیجه: my-plugin/includes/class-loader.php

به همین دلیل تعریف constant سراسری در فایل اصلی ضروری است.

نبود بررسی defined برای constant

اگر یک constant را بدون if ( ! defined() ) تعریف کنید و افزونه در محیطی چند بار بارگذاری شود، خطا رخ می‌دهد:

// اشتباه
define( 'MYPLUGIN_BASENAME', plugin_basename( __FILE__ ) );

// درست
if ( ! defined( 'MYPLUGIN_BASENAME' ) ) {
    define( 'MYPLUGIN_BASENAME', plugin_basename( __FILE__ ) );
}

نبود escape در نمایش basename

اگر basename را در HTML نمایش می‌دهید، حتماً escape کنید:

echo esc_html( MYPLUGIN_BASENAME );

مطلب Output Escaping در وردپرس راهنمای کامل است.

فراموش کردن trailing slash در زمان ترکیب مسیر

در الگوی بارگذاری فایل ترجمه، باید dirname( plugin_basename( __FILE__ ) ) استفاده شود، نه خود basename:

// اشتباه
load_plugin_textdomain( 'my-plugin', false, plugin_basename( __FILE__ ) . '/languages' );

// درست
load_plugin_textdomain( 'my-plugin', false, dirname( plugin_basename( __FILE__ ) ) . '/languages' );

نبود تست روی سناریوهای مرزی

تست‌هایی مثل «افزونه در پوشه با فاصله»، «افزونه در پوشه اصلی plugins»، «افزونه در زیرپوشه» و «نصب از طریق symlink» را حتماً بنویسید.

امنیت و عملکرد در plugin_basename

این تابع به‌تنهایی امنیت را تهدید نمی‌کند، اما چند نکته مهم دارد:

  • مسیر فیزیکی یا basename را در frontend افشا نکنید
  • در لاگ‌های عمومی، basename کامل را ثبت نکنید
  • در فایل اصلی افزونه، چک defined( 'ABSPATH' ) را قرار دهید
  • basename را به‌عنوان شناسه امن در نظر نگیرید و از آن برای احراز هویت استفاده نکنید
if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

برای مطالعه جامع مباحث امنیتی، مطلب SQL Injection Prevention در وردپرس و مسدود کردن دسترسی مستقیم به فایل‌ها مرجع هستند.

از نظر عملکرد، این تابع نسبت به plugin_dir_path و plugin_dir_url کمی سنگین‌تر است چون در پیاده‌سازی داخلی خود، آرایه‌ای از مسیرهای پلاگین‌ها را می‌سازد و cache می‌کند. اگر در حلقه‌های پرتکرار فراخوانی شود، این cache هر بار بازسازی نمی‌شود و از این نظر عملکرد خوبی دارد. اما توصیه می‌شود مقدار را در constant ذخیره کنید:

if ( ! defined( 'MYPLUGIN_BASENAME' ) ) {
    define( 'MYPLUGIN_BASENAME', plugin_basename( __FILE__ ) );
}

برای مطالعه الگوهای بهینه، مطلب استانداردهای PHP توصیه می‌شود.

پرسش‌های پرتکرار درباره plugin_basename

تفاوت plugin_basename با plugin_dir_path چیست؟

plugin_basename() مسیر نسبی فایل اصلی افزونه را از پوشه wp-content/plugins برمی‌گرداند و به‌عنوان شناسه یکتا استفاده می‌شود، در حالی که plugin_dir_path() مسیر فیزیکی مطلق پوشه افزونه را برمی‌گرداند.

چرا از basename در نام هوک‌ها استفاده می‌شود؟

چون basename شامل نام پوشه افزونه است و به همین دلیل یکتا است. این یکتایی از تداخل بین افزونه‌ها جلوگیری می‌کند.

آیا می‌توان basename را به کاربر نمایش داد؟

فنی ممکن است اما توصیه نمی‌شود چون جزئیات ساختاری افزونه را افشا می‌کند. اگر نیاز به نمایش دارید، حتماً با esc_html escape کنید.

آیا این تابع روی سایت لوکال کار می‌کند؟

بله، در سایت لوکال مثل Local، XAMPP یا Docker نیز به‌درستی کار می‌کند. اما در محیط‌های لوکال با ساختار پوشه‌ای متفاوت، ممکن است basename مقدار متفاوتی داشته باشد.

آیا این تابع روی Multisite کار می‌کند؟

بله، در Multisite، افزونه در سطح شبکه نصب می‌شود و basename یکسان است. برای فیلترهای سطح شبکه، از network_admin_plugin_action_links_ استفاده کنید.

آیا می‌توان از این تابع برای افزونه‌های MU استفاده کرد؟

بله، برای افزونه‌های Must-Use نیز کار می‌کند. اما مسیر پوشه mu-plugins متفاوت است و ساختار پوشه‌ای تخت دارد.

آیا این تابع در فایل‌های تست واحد کاربرد دارد؟

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

نگاه فنی عمیق به plugin_basename

در سطح معماری، plugin_basename() یکی از پیچیده‌ترین توابع ساده در وردپرس است. این تابع در فایل wp-includes/plugin.php تعریف شده و از یک cache سراسری برای مسیرهای پلاگین‌ها استفاده می‌کند. این cache در متغیر $wp_plugin_paths ذخیره می‌شود.

نکته ظریف اول، مسئله symlink است. اگر پوشه افزونه از طریق symlink نصب شده باشد، مسیر فیزیکی با مسیر منطقی متفاوت است. تابع plugin_basename این تفاوت را با استفاده از آرایه $wp_plugin_paths حل می‌کند. اما این راه‌حل کامل نیست و در برخی پیکربندی‌های پیچیده می‌تواند رفتار غیرمنتظره داشته باشد.

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

مسئله سوم، رفتار این تابع در محیط‌های با ساختار پوشه‌ای غیر استاندارد است. برخی پیکربندی‌های سرور از WP_PLUGIN_DIR و WP_PLUGIN_URL سفارشی استفاده می‌کنند. در این حالت، basename همچنان بر اساس ساختار پیش‌فرض محاسبه می‌شود اما ممکن است با آنچه انتظار دارید تفاوت داشته باشد.

در نهایت، در پروژه‌های Enterprise توصیه می‌شود یک Plugin_Identity اختصاصی بسازید که basename، dir و url را در یک ساختار یکپارچه ترکیب کند:

class MyPlugin_Identity {
    public static function basename() {
        return MYPLUGIN_BASENAME;
    }

    public static function dir() {
        return MYPLUGIN_DIR;
    }

    public static function url() {
        return MYPLUGIN_URL;
    }
}

این الگو از پخش شدن مسیرها در فایل‌های مختلف جلوگیری می‌کند و تست‌پذیری را بالا می‌برد. برای مطالعه بیشتر، مباحث توابع وردپرس برای کار با فایل‌ها و استانداردهای PSR مفید هستند. برای مطالعه بیشتر درباره خود وردپرس، WordPress در ویکی‌پدیا نقطه شروع خوبی است.

اگر در پروژه‌ای با مشکل تداخل basename یا رفتار غیرمنتظره در symlink مواجه شده‌اید، برای ما جالب است بدانید کدام راهکار عملاً به حل مسئله کمک کرده است. تجربه خود را در دیدگاه‌ها بنویسید تا برای سایر توسعه‌دهندگان هم مفید باشد.