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

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

در پروژه‌هایی که پنل تنظیمات سنگین داشتند، انتخاب درست slug، icon و position همیشه تفاوت میان یک افزونه حرفه‌ای و یک افزونه آماتور بوده است. جای اشتباه در منوی مدیریت، می‌تواند کاربر را گیج کند یا حتی با افزونه‌های محبوب دیگر تداخل ایجاد کند.

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

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

بدون این تابع، افزونه‌ها مجبور بودند URLهای سفارشی بسازند یا فایل‌های مستقل در پوشه wp-admin قرار دهند که هر دو رویکرد با استانداردهای امنیتی وردپرس ناسازگار است. این تابع هم امنیت را تضمین می‌کند و هم دسترسی کاربران را بر اساس capability کنترل می‌کند.

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

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

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

add_menu_page( string $page_title, string $menu_title, string $capability, string $menu_slug, callable $function = '', string $icon_url = '', int|float $position = null ): string

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

$hook = add_menu_page( ... );
add_action( 'admin_print_scripts-' . $hook, 'myplugin_admin_scripts' );

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

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

پارامتر page_title

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

add_menu_page(
    'تنظیمات پیشرفته افزونه من',  // page_title
    'افزونه من',                  // menu_title
    'manage_options',
    'myplugin-settings',
    'myplugin_render_settings_page'
);

پارامتر menu_title

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

'menu_title' => 'محصولات من'

در صورت نیاز به آیکون HTML در عنوان منو، می‌توانید از تگ <span> استفاده کنید، اما توصیه نمی‌شود.

پارامتر capability

مهم‌ترین پارامتر امنیتی. مشخص می‌کند چه کاربرانی به این صفحه دسترسی دارند. مقادیر رایج: manage_options برای مدیران، edit_posts برای نویسندگان، publish_posts برای ناشرها:

add_menu_page(
    'تنظیمات',
    'تنظیمات من',
    'manage_options',  // فقط مدیران دسترسی دارند
    'myplugin-settings',
    'myplugin_render'
);

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

پارامتر menu_slug

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

add_menu_page( ... , 'myplugin-settings', ... );

URL نهایی چیزی شبیه admin.php?page=myplugin-settings خواهد بود.

پارامتر function

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

function myplugin_render_settings_page() {
    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 در وردپرس راهنمای کامل است.

پارامتر icon_url

آیکون منو در کناری پنل. می‌توانید از Dashicons استفاده کنید یا از یک تصویر URL بدهید:

add_menu_page( ... , 'dashicons-admin-generic', ... );

پارامتر position

مکان منو در کناری پنل. اگر null بگذارید، منو در پایین قرار می‌گیرد. مقادیر رایج:

  • 2: داشبورد
  • 4: جداکننده اول
  • 5: نوشته‌ها
  • 10: رسانه
  • 20: برگه‌ها
  • 25: دیدگاه‌ها
  • 60: اولین جداکننده پایین
  • 80: تنظیمات
  • 99: جداکننده پایانی

اگر عددی بین مقادیر بالا انتخاب کنید، منو بین آن‌ها قرار می‌گیرد.

انتخاب icon و position مناسب

انتخاب درست icon و position نه‌تنها روی تجربه بصری اثر دارد، بلکه بر اولویت درک‌شده منو توسط کاربر هم تأثیر می‌گذارد:

Dashicons پیشنهادی

  • dashicons-admin-generic: برای تنظیمات عمومی
  • dashicons-chart-line: برای گزارش‌ها
  • dashicons-cart: برای فروشگاه
  • dashicons-email: برای ایمیل
  • dashicons-groups: برای کاربران
  • dashicons-shield: برای امنیت

فهرست کامل Dashicons در مطلب راهنمای WordPress Components در دسترس است.

تداخل position با افزونه‌های محبوب

افزونه‌های محبوب مثل WooCommerce و Elementor از positionهای خاصی استفاده می‌کنند. برای جلوگیری از تداخل، بهتر است مقدار دقیق را از آن‌ها دور نگه دارید. WooCommerce معمولاً از position 55 و 56 استفاده می‌کند.

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

ساخت منوی پایه افزونه

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

function myplugin_render_settings_page() {
    if ( ! current_user_can( 'manage_options' ) ) {
        return;
    }
    echo '<div class="wrap">';
    echo '<h1>' . esc_html( get_admin_page_title() ) . '</h1>';
    echo '<p>' . esc_html__( 'به صفحه تنظیمات افزونه خوش آمدید.', 'my-plugin' ) . '</p>';
    echo '</div>';
}

ساخت منو با صفحه تنظیمات کامل

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

function myplugin_render_settings_page() {
    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 انجام می‌شود.

ساخت صفحه تنظیمات زیر منوی Settings

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

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

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

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

استفاده از WP-CLI برای ساخت منو

در برخی سناریوها، ساخت منو از طریق WP-CLI یا اسکریپت می‌تواند مفید باشد. مطلب راهنمای WP-CLI الگوهای این کار را پوشش می‌دهد.

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

نبود capability صحیح

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

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

اگر صفحه شامل فرم‌هایی است که داده ذخیره می‌کنند، باید nonce داشته باشند. اگر از Settings API استفاده می‌کنید، settings_fields() nonce را اضافه می‌کند. اما در فرم‌های سفارشی، باید خودتان این کار را انجام دهید. مطلب Nonce در وردپرس را ببینید.

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

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

echo esc_html( get_option( 'myplugin_note' ) );

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

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

انتخاب position تداخل‌دار

انتخاب positionهای رایج مثل 5، 10، 20 بدون دقت می‌تواند منو را در جای اشتباه قرار دهد. اگر position مهم نیست، از null استفاده کنید.

نبود بررسی دسترسی در callback

حتی اگر capability در add_menu_page تنظیم شده باشد، در ابتدای callback نیز باید current_user_can بررسی شود چون ممکن است URL صفحه به‌طور مستقیم باز شود.

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

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

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

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

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

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

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

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

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

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

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

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

چرا صفحه من در منو ظاهر نمی‌شود؟

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

آیا می‌توان منو را در سمت راست پنل قرار داد؟

خیر، منوی وردپرس همیشه در سمت چپ است. اما می‌توانید با تغییر position، جای منو در این کناری را تغییر دهید.

آیا می‌توان چند منو با یک callback ساخت؟

بله، چند منو می‌توانند یک callback مشترک داشته باشند. اما برای وضوح کد توصیه می‌شود هر منو callback اختصاصی داشته باشد.

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

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

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

خیر، Dashicons مجموعه بسته‌ای است. اما می‌توانید به‌جای آن URL یک تصویر SVG را در پارامتر icon_url بدهید.

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

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

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

در سطح معماری، add_menu_page() یک رکورد در آرایه سراسری $menu ثبت می‌کند که در متغیر سراسری $GLOBALS['menu'] قابل دسترسی است. وردپرس در زمان رندر پنل، این آرایه را می‌پیماید و برای هر رکورد، منو را می‌سازد. همچنین یک زیرمنوی پیش‌فرض به منو اضافه می‌کند تا امکان بازگشت به صفحه اصلی وجود داشته باشد.

نکته ظریف اول، رفتار hook_suffix خروجی این تابع است. این رشته از الگوی toplevel_page_ + slug تشکیل شده و می‌تواند برای بارگذاری اسکریپت‌ها استفاده شود. اگر آن را ذخیره نکنید، هر بار باید از get_current_screen استفاده کنید که کندتر است.

نکته دوم، تعامل منو با سیستم Permission است. اگرچه capability در add_menu_page تعریف می‌شود، اما این تنظیم فقط برای نمایش منو در کناری است. برای محافظت واقعی از صفحه، باید در callback دوباره بررسی شود. اگر فراموش شود، کاربر می‌تواند با URL مستقیم به صفحه دسترسی پیدا کند.

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

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

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