تابع add_submenu_page() در وردپرس ابزار رسمی افزودن یک زیرمنو به منوی موجود در پنل مدیریت است و به‌عنوان یکی از پرکاربردترین توابع در ساخت پنل‌های چندصفحه‌ای افزونه‌ها شناخته می‌شود. بدون این تابع، سازمان‌دهی صفحات افزونه در منوی مستقل امکان‌پذیر نیست.

تابع add_submenu_page وردپرس یکی از پرکاربردترین توابع مدیریت منو برای ساخت زیرمنو در پنل مدیریت است. این تابع امکان تعریف parent_slug، عنوان، capability و callback را فراهم می‌کند و پایه ساخت پنل‌های چندصفحه‌ای محسوب می‌شود. در این راهنما ساختار کامل، پارامترها، نمونه‌های واقعی، اشتباهات رایج و نکات امنیتی این تابع بررسی می‌شود. همچنین تفاوت آن با add_menu_page و add_options_page توضیح داده می‌شود. در پایان پرسش‌های پرتکرار و نگاه فنی عمیق به این تابع مرور خواهد شد.

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

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

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

علاوه بر این، این تابع می‌تواند زیرمنوهایی به منوهای پیش‌فرض وردپرس مثل Settings و Tools اضافه کند. این قابلیت بسیار رایج است و به یکپارچگی تجربه کاربری کمک می‌کند. برای مطالعه این سناریو، مطلب تابع add_options_page را ببینید.

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

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

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

add_submenu_page( string $parent_slug, string $page_title, string $menu_title, string $capability, string $menu_slug, callable $function = '', int|float $position = null ): string|false

خروجی این تابع hook_suffix صفحه است یا در صورت خطا مقدار false. این مقدار برای بارگذاری اسکریپت‌ها فقط در همان صفحه بسیار مفید است:

$hook = add_submenu_page( ... );
add_action( 'admin_print_scripts-' . $hook, 'myplugin_submenu_scripts' );

برای آشنایی با بارگذاری اسکریپت‌ها، مطلب هوک admin_enqueue_scripts را ببینید.

پارامترهای کلیدی و کاربرد هرکدام

پارامتر parent_slug

مهم‌ترین پارامتر متمایزکننده این تابع. slug منوی والدی که زیرمنو به آن اضافه می‌شود. اگر می‌خواهید زیرمنوی تنظیمات به منوی Settings وردپرس اضافه شود، از مقدار options-general.php استفاده کنید:

add_submenu_page(
    'options-general.php',  // parent_slug
    'تنظیمات افزونه من',
    'افزونه من',
    'manage_options',
    'myplugin-settings',
    'myplugin_render'
);

پارامتر page_title

عنوانی که در تگ title مرورگر و در بالای صفحه نمایش داده می‌شود. این مقدار می‌تواند طولانی‌تر از menu_title باشد:

add_submenu_page(
    'myplugin-main',
    'گزارش‌های پیشرفته افزونه من',
    'گزارش‌ها',
    'manage_options',
    'myplugin-reports',
    'myplugin_render_reports'
);

پارامتر menu_title

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

'menu_title' => 'گزارش‌ها'

پارامتر capability

سطح دسترسی موردنیاز برای مشاهده و دسترسی به این صفحه. مقادیر رایج: manage_options، edit_posts، publish_posts:

add_submenu_page(
    'myplugin-main',
    'گزارش‌ها',
    'گزارش‌ها',
    'manage_options',  // فقط مدیران دسترسی دارند
    'myplugin-reports',
    'myplugin_render_reports'
);

برای مطالعه کامل نقش‌ها و capability، مطلب Capability و نقش‌های کاربری سفارشی را ببینید.

پارامتر menu_slug

شناسه یکتای صفحه. این مقدار در URL پنل مدیریت ظاهر می‌شود و باید یکتا باشد. اگر با slug دیگری تداخل داشته باشد، ممکن است منو نمایش داده نشود:

'menu_slug' => 'myplugin-reports'

پارامتر function

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

function myplugin_render_reports() {
    if ( ! current_user_can( 'manage_options' ) ) {
        return;
    }
    echo '<div class="wrap"><h1>' . esc_html( get_admin_page_title() ) . '</h1></div>';
}

استفاده از esc_html برای escape کردن عنوان ضروری است. مطلب Output Escaping در وردپرس راهنمای کامل است.

پارامتر position

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

add_submenu_page( ... , 10 );  // زیرمنو در موقعیت 10 قرار می‌گیرد

انتخاب درست parent_slug و کاربردهای رایج

انتخاب parent_slug بستگی به این دارد که می‌خواهید زیرمنو کجا ظاهر شود. رایج‌ترین مقادیر عبارتند از:

زیرمنو زیر منوی مستقل افزونه

اگر منوی مستقل دارید، مقدار parent_slug همان slug منوی اصلی است:

add_menu_page( 'تنظیمات اصلی', 'افزونه من', 'manage_options', 'myplugin-main', 'myplugin_main_render' );
add_submenu_page( 'myplugin-main', 'گزارش‌ها', 'گزارش‌ها', 'manage_options', 'myplugin-reports', 'myplugin_reports_render' );
add_submenu_page( 'myplugin-main', 'راهنما', 'راهنما', 'manage_options', 'myplugin-help', 'myplugin_help_render' );

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

زیرمنو زیر منوی Settings وردپرس

برای افزودن صفحه تنظیمات در زیرمنوی Settings، از options-general.php استفاده کنید:

add_submenu_page(
    'options-general.php',
    'تنظیمات افزونه من',
    'افزونه من',
    'manage_options',
    'myplugin-settings',
    'myplugin_settings_render'
);

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

زیرمنو زیر منوی Tools

برای ابزارهای اجرایی یا پاک‌سازی، زیرمنوی Tools جای مناسبی است:

add_submenu_page(
    'tools.php',
    'ابزارهای افزونه من',
    'افزونه من',
    'manage_options',
    'myplugin-tools',
    'myplugin_tools_render'
);

زیرمنو زیر منوی Users

برای صفحات مرتبط با کاربران:

add_submenu_page(
    'users.php',
    'گزارش‌های کاربران',
    'گزارش کاربران',
    'list_users',
    'myplugin-user-reports',
    'myplugin_user_reports_render'
);

زیرمنو زیر منوی WooCommerce

برای افزونه‌های ووکامرس، slug منوی اصلی woocommerce است:

add_submenu_page(
    'woocommerce',
    'گزارش‌های پیشرفته',
    'گزارش پیشرفته',
    'manage_woocommerce',
    'myplugin-woo-reports',
    'myplugin_woo_reports_render'
);

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

ساخت منوی مستقل با دو زیرمنو

add_action( 'admin_menu', function () {
    add_menu_page(
        'تنظیمات افزونه من',
        'افزونه من',
        'manage_options',
        'myplugin-main',
        'myplugin_main_render',
        'dashicons-admin-generic',
        80
    );

    add_submenu_page(
        'myplugin-main',
        'گزارش‌ها',
        'گزارش‌ها',
        'manage_options',
        'myplugin-reports',
        'myplugin_reports_render'
    );

    add_submenu_page(
        'myplugin-main',
        'راهنما',
        'راهنما',
        'manage_options',
        'myplugin-help',
        'myplugin_help_render'
    );
} );

افزودن تنظیمات به زیرمنوی Settings

رویکرد توصیه‌شده برای افزونه‌های سبک، استفاده از تابع add_options_page است که در واقع wrapper روی همین تابع است و parent_slug را به‌طور خودکار روی options-general.php تنظیم می‌کند.

افزودن زیرمنوی ابزار به منوی Tools

add_action( 'admin_menu', function () {
    add_submenu_page(
        'tools.php',
        'ابزارهای من',
        'ابزارهای من',
        'manage_options',
        'myplugin-tools',
        'myplugin_tools_render'
    );
} );

بارگذاری اسکریپت فقط در زیرمنو

$hook = add_submenu_page( ... );
add_action( 'admin_print_scripts-' . $hook, function () {
    wp_enqueue_script( 'myplugin-reports', plugin_dir_url( __FILE__ ) . 'reports.js', array( 'jquery' ), '1.0.0', true );
} );

این الگو از بارگذاری اسکریپت در تمام پنل جلوگیری می‌کند. مطلب تابع wp_enqueue_script راهنماست.

ساخت زیرمنو برای Custom Post Type

اگر می‌خواهید زیرمنو به یک post type خاص مرتبط باشد، مطلب تابع register_post_type راهنماست.

ترکیب با Settings API

برای ساخت صفحه تنظیمات کامل در زیرمنو، از Settings API استفاده کنید:

function myplugin_settings_render() {
    if ( ! current_user_can( 'manage_options' ) ) {
        return;
    }
    ?>
    <div class="wrap">
        <h1><?php echo esc_html( get_admin_page_title() ); ?></h1>
        <form method="post" action="options.php">
            <?php
            settings_fields( 'myplugin_settings' );
            do_settings_sections( 'myplugin_settings' );
            submit_button();
            ?>
        </form>
    </div>
    <?php
}

برای مطالعه بخش‌ها و فیلدها، مطالب تابع register_setting، تابع add_settings_section و تابع add_settings_field را ببینید.

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

نبود capability صحیح

شایع‌ترین اشتباه. اگر capability را read بگذارید، هر کاربر وارد‌شده به سایت می‌تواند به صفحه دسترسی پیدا کند. همیشه از manage_options برای صفحات تنظیمات استفاده کنید.

parent_slug اشتباه

اگر parent_slug به یک منوی ناموجود اشاره کند، زیرمنو نمایش داده نمی‌شود و وردپرس مقدار false برمی‌گرداند. همیشه مطمئن شوید که منوی والد در همان hook یا hook قبلی ثبت شده است.

نبود nonce در فرم‌های داخل صفحه

اگر صفحه شامل فرم‌هایی است که داده ذخیره می‌کنند، باید nonce داشته باشند. مطلب Nonce در وردپرس را ببینید.

نبود escape در خروجی HTML

هر مقدار چاپ‌شده باید escape شود. عدم escape به XSS منجر می‌شود، به‌ویژه اگر مقدار از دیتابیس خوانده شود.

تداخل slug با افزونه دیگر

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

فراموشی افزودن زیرمنوی اول

وقتی یک منوی مستقل با add_menu_page می‌سازید، وردپرس یک زیرمنوی خودکار با عنوان منو اضافه می‌کند. اگر می‌خواهید این زیرمنوی خودکار را با نام دلخواه جایگزین کنید، باید ابتدا آن را حذف و زیرمنوی جدید اضافه کنید:

add_action( 'admin_menu', function () {
    add_menu_page( ... );
    remove_submenu_page( 'myplugin-main', 'myplugin-main' );
    add_submenu_page( 'myplugin-main', 'صفحه اصلی', 'صفحه اصلی', 'manage_options', 'myplugin-main', 'myplugin_main_render' );
}, 99 );

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

تست‌هایی مثل «کاربر بدون دسترسی»، «parent_slug ناموجود»، «slug تکراری» و «callback نامعتبر» را حتماً بنویسید.

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

این تابع به‌تنهایی امنیت را تضمین نمی‌کند. لایه‌های ضروری امنیتی عبارتند از:

  • capability صحیح در add_submenu_page
  • بررسی مجدد current_user_can در callback
  • nonce در فرم‌های داخل صفحه
  • escape کامل خروجی HTML
  • sanitize ورودی‌های فرم با Settings API

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

از نظر عملکرد، این تابع هزینه قابل توجهی ندارد چون فقط یک رکورد در آرایه منو ثبت می‌کند. اما callback صفحه می‌تواند سنگین باشد اگر کوئری‌های پیچیده اجرا کند. توصیه می‌شود:

  1. callback صفحه را سبک نگه دارید
  2. از کش برای داده‌های پرتکرار استفاده کنید
  3. اسکریپت‌ها و استایل‌ها را فقط در همان صفحه بارگذاری کنید

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

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

تفاوت add_submenu_page با add_menu_page چیست؟

add_menu_page() یک منوی سطح اول با آیکون در کناری پنل می‌سازد، در حالی که add_submenu_page() یک زیرمنو در دل یک منوی موجود اضافه می‌کند.

چرا زیرمنو من نمایش داده نمی‌شود؟

معمولاً به سه دلیل: parent_slug اشتباه، capability کاربر کافی نیست، یا تابع در hook admin_menu فراخوانی نشده است.

آیا می‌توان به منوهای پیش‌فرض وردپرس زیرمنو اضافه کرد؟

بله، با مقدار مناسب parent_slug. مثلاً options-general.php برای Settings، tools.php برای Tools و users.php برای Users.

آیا می‌توان زیرمنوی خودکار منو را حذف کرد؟

بله، با remove_submenu_page. این الگو برای جایگزینی زیرمنوی خودکار با نام دلخواه رایج است.

آیا می‌توان زیرمنو را برای نقش خاصی مخفی کرد؟

بله، با تنظیم capability مناسب. کاربرانی که آن capability را ندارند، زیرمنو را نمی‌بینند.

آیا می‌توان ترتیب زیرمنوها را تغییر داد؟

بله، با پارامتر position. مقادیر کوچک‌تر بالاتر نمایش داده می‌شوند. اگر position یکسان باشد، ترتیب فراخوانی ملاک است.

آیا add_submenu_page با Multisite سازگار است؟

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

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

در سطح معماری، add_submenu_page() یک رکورد در آرایه سراسری $submenu ثبت می‌کند که در متغیر سراسری $GLOBALS['submenu'] قابل دسترسی است. کلید اصلی این آرایه، parent_slug است. وردپرس در زمان رندر پنل، برای هر منوی اصلی، آرایه زیرمنوهای مربوطه را می‌پیماید و رندر می‌کند.

نکته ظریف اول، مسئله ترتیب اجرای hook است. تمام توابع add_menu_page و add_submenu_page باید در hook admin_menu فراخوانی شوند. اگر دیرتر فراخوانی شوند، منو در پنل ظاهر نمی‌شود. برخی افزونه‌های پیچیده از priority بالاتر (مثلاً 99) استفاده می‌کنند تا پس از افزونه‌های دیگر ثبت شوند.

نکته دوم، رفتار پیش‌فرض وردپرس در افزودن زیرمنوی خودکار است. هر add_menu_page یک زیرمنوی خودکار با همان slug ایجاد می‌کند. اگر شما زیرمنوی دیگری با همان slug اضافه کنید، زیرمنوی خودکار با زیرمنوی شما ادغام می‌شود. اگر می‌خواهید عنوان زیرمنوی اول متفاوت باشد، باید آن را حذف و دوباره اضافه کنید.

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

در نهایت، در پروژه‌های Enterprise توصیه می‌شود یک Registry مرکزی برای منوها بسازید که در آن تمام منوها و زیرمنوها به‌صورت declarative تعریف شوند. این کار از پخش شدن منطق در چند فایل جلوگیری می‌کند و امکان افزودن قابلیت‌هایی مثل Feature Toggle برای منوها را فراهم می‌کند. برای مطالعه بیشتر، مباحث WordPress Components و توابع وردپرس برای مدیریت منوها مفید هستند. برای مطالعه بیشتر درباره خود وردپرس، WordPress در ویکی‌پدیا نقطه شروع خوبی است.

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