تابع add_options_page() در وردپرس ابزار استاندارد افزودن صفحه تنظیمات به زیرمنوی Settings است و به‌عنوان یک wrapper سبک روی add_submenu_page، مسیر ساده‌تری برای افزونه‌های سبک فراهم می‌کند. بدون این تابع، هر افزونه باید خودش slug منوی Settings را مدیریت کند که پیچیدگی غیرضروری ایجاد می‌کند.

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

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

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

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

تابع add_options_page() دقیقاً برای همین کار طراحی شده است. این تابع در واقع یک wrapper نازک روی add_submenu_page است که مقدار parent_slug را به‌طور خودکار روی options-general.php تنظیم می‌کند. به همین دلیل کد کوتاه‌تر و خواناتر می‌شود.

برای درک کامل این الگو، مطالب تابع add_menu_page و تابع add_submenu_page را مطالعه کنید.

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

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

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

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

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

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

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

پارامتر page_title

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

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

پارامتر menu_title

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

'menu_title' => 'افزونه من'

پارامتر capability

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

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

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

پارامتر menu_slug

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

'menu_slug' => 'myplugin-settings'

URL نهایی چیزی شبیه options-general.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 در وردپرس راهنمای کامل است.

پارامتر position

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

add_options_page( ... , 10 );

تفاوت با add_menu_page و add_submenu_page

این سه تابع نقش‌های متفاوتی در ساخت منوی پنل مدیریت دارند. انتخاب درست بستگی به سناریو دارد:

تابعکاربردparent_slug
add_menu_pageمنوی سطح اول با آیکونندارد
add_submenu_pageزیرمنو در هر منوی موجودالزامی
add_options_pageزیرمنوی Settingsخودکار

به‌طور خلاصه: اگر افزونه سبک است و فقط صفحه تنظیمات دارد، add_options_page انتخاب درست است. اگر افزونه چند صفحه دارد و نیاز به منوی مستقل دارد، از add_menu_page و add_submenu_page استفاده کنید.

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

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

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

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
}

ثبت تنظیمات با Settings API

add_action( 'admin_init', function () {
    register_setting( 'myplugin_settings', 'myplugin_api_key', array(
        'sanitize_callback' => 'sanitize_text_field',
    ) );

    add_settings_section(
        'myplugin_main',
        'تنظیمات اصلی',
        '__return_null',
        'myplugin_settings'
    );

    add_settings_field(
        'myplugin_api_key',
        'کلید API',
        function () {
            printf(
                '<input type="text" name="myplugin_api_key" value="%s" class="regular-text" />',
                esc_attr( get_option( 'myplugin_api_key' ) )
            );
        },
        'myplugin_settings',
        'myplugin_main'
    );
} );

برای مطالعه دقیق این توابع، مطالب تابع register_setting، تابع add_settings_section و تابع add_settings_field را ببینید.

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

$hook = add_options_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 راهنماست.

افزودن لینک Settings در صفحه افزونه‌ها

یکی از UXهای مهم افزونه‌های حرفه‌ای، افزودن لینک مستقیم «تنظیمات» در فهرست افزونه‌هاست:

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

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

پاک‌سازی تنظیمات در زمان غیرفعال‌سازی

برای حذف مقادیر در زمان غیرفعال‌سازی افزونه، از تابع register_deactivation_hook و تابع delete_option استفاده کنید.

ساخت صفحه تنظیمات چندزبانه

اگر افزونه چندزبانه است، از توابع بین‌المللی‌سازی مثل esc_html__ و esc_attr__ استفاده کنید. مطلب راهنمای Sanitization راهنماست.

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

نبود capability صحیح

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

نبود nonce در فرم

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

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

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

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

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

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

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

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

افزونه‌های حرفه‌ای، لینک مستقیم به تنظیمات را در فهرست افزونه‌ها اضافه می‌کنند. بدون این لینک، کاربر باید مسیر طولانی از منوی Settings تا صفحه تنظیمات را طی کند.

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

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

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

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

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

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

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

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

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

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

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

add_options_page() یک زیرمنو مخصوص منوی Settings ایجاد می‌کند و parent_slug را به‌طور خودکار تنظیم می‌کند، در حالی که add_submenu_page() عمومی‌تر است و به هر منوی موجود می‌تواند متصل شود.

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

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

آیا می‌توان صفحه راهنما هم در زیرمنوی Settings داشت؟

بله، چند add_options_page می‌توانند زیر منوی Settings ثبت شوند. برای ترتیب دلخواه، از پارامتر position استفاده کنید.

آیا می‌توان لینک تنظیمات را در فهرست افزونه‌ها اضافه کرد؟

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

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

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

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

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

آیا می‌توان از add_options_page در قالب استفاده کرد؟

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

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

در سطح معماری، add_options_page() یک wrapper نازک است که درون آن، add_submenu_page با parent_slug ثابت options-general.php فراخوانی می‌شود. این تابع خروجی hook_suffix را برمی‌گرداند که می‌توان از آن برای بارگذاری اسکریپت‌ها استفاده کرد.

نکته ظریف اول، مسئله position در زیرمنوی Settings است. اگر position را null بگذارید، صفحه در انتهای فهرست قرار می‌گیرد. اگر عدد بدهید، بین آیتم‌های موجود وردپرس قرار می‌گیرد. آیتم‌های پیش‌فرض Settings وردپرس موقعیت‌های 10 (General)، 20 (Writing)، 25 (Reading)، 30 (Discussion)، 40 (Media)، 43 (Permalinks) و 50 (Privacy) را اشغال می‌کنند.

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

مسئله سوم، مسئله ترجمه slug است. slug در URL نمایش داده می‌شود و باید لاتین باشد. اما صفحه_title و menu_title می‌توانند فارسی باشند و از توابع ترجمه استفاده کنند:

add_options_page(
    esc_html__( 'تنظیمات پیشرفته', 'my-plugin' ),
    esc_html__( 'افزونه من', 'my-plugin' ),
    'manage_options',
    'myplugin-settings',
    'myplugin_render'
);

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

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