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

متاباکس چیست و چه زمانی لازم است؟

متاباکس، یک ظرف گرافیکی در پیشخوان وردپرس است که فیلدهای ورودی دلخواه شما را در خود جای می‌دهد. این جعبه، در صفحهٔ ویرایش هر نوع محتوا (نوشته، برگه، CPT، یا حتی محصول ووکامرس) قابل افزودن است. چهار سناریوی اصلی نیاز به متاباکس: یک — اطلاعات ساختاریافته. مثال: نوشته‌های خبری که نیاز به «منبع» یا «تاریخ رویداد» دارند. دو — تنظیمات رفتار محتوا. مثال: نوشته‌ای که باید در اسلایدر صفحهٔ اصلی نمایش داده شود یا نه. سه — داده‌های پویا. مثال: فرمی که با ارسال، فیلدهای ورودی را به متادیتای پست تبدیل می‌کند. چهار — داده‌های مخصوص نوع محتوا. مثال: CPT «پروژه» با فیلد «مشتری» و «سال اجرا». دو گزینهٔ دیگر برای فیلد سفارشی هم وجود دارد: باکس Custom Fields پیش‌فرض وردپرس که ساده است ولی رابط کاربری خامی دارد، و ACF که رابط گرافیکی زیبایی دارد. متاباکس اختصاصی، جایگاه میانی این دو است: کنترل کامل بدون وابستگی به افزونه. مقایسهٔ این سه مسیر در ساخت فیلدهای سفارشی در وردپرس آمده است.

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

کجا متاباکس را بنویسیم؟

سه مسیر برای قرار دادن کد متاباکس: یک — افزونهٔ اختصاصی. انتخاب درست برای هر متاباکسی که باید سال‌ها بماند. راهنمای کامل در کدنویسی اختصاصی افزونه و توسعهٔ افزونه از صفر. دو — mu-plugins. برای متاباکس‌های زیرساختی که نباید کاربر خاموششان کند. سه — چایلد تم. فقط برای متاباکس‌های کاملاً ظاهری که وابسته به قالب فعلی هستند. در تجربه‌های میدانی من، هر متاباکسی که منطق دارد و به دادهٔ اختصاصی می‌نویسد، بدون استثنا باید در افزونه باشد. اگر در فایل قالب والد نوشته شود، روز تغییر قالب، داده باقی می‌ماند ولی رابط کاربری ویرایش از دست می‌رود. ساختار استاندارد پوشه‌ها در ساختار فایل‌های افزونهٔ استاندارد آمده است.

ثبت متاباکس با add_meta_box

ثبت متاباکس، با تابع add_meta_box و روی هوک add_meta_boxes انجام می‌شود:

function my_plugin_add_meta_box() {
    add_meta_box(
        'my_source_box',             // id یکتا
        'اطلاعات منبع',              // عنوان
        'my_plugin_render_source_box', // callback رندر
        array( 'post', 'article' ),   // نوع محتوا (چندگانه)
        'normal',                     // مکان نمایش
        'high'                        // اولویت
    );
}
add_action( 'add_meta_boxes', 'my_plugin_add_meta_box' );

پارامترهای کلیدی: یک — id: شناسهٔ یکتا برای جعبه. حتماً با پیشوند افزونه. دو — title: عنوانی که در بالای جعبه دیده می‌شود. سه — callback: تابعی که محتوای جعبه را رندر می‌کند. چهار — screen: نوع محتواها؛ می‌تواند رشته یا آرایه باشد. پنج — context: مکان نمایش — normal (زیر ویرایشگر)، side (ستون کنار)، advanced (زیر جعبه‌های استاندارد). شش — priority: اولویت — high، core، default، low. راهنمای هوک‌ها در هوک‌های وردپرس، تفاوت اکشن و فیلتر، و نحوهٔ استفاده از add_action. انتخاب context باید آگاهانه باشد: فیلدهای کوتاه در side، فیلدهای طولانی یا پیچیده در normal.

رندر متاباکس و انواع فیلد

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

function my_plugin_render_source_box( $post ) {
    wp_nonce_field( 'my_plugin_save_source', 'my_plugin_source_nonce' );

    $source = get_post_meta( $post->ID, '_my_source', true );
    $url    = get_post_meta( $post->ID, '_my_source_url', true );
    ?>
    <div class="my-metabox">
        <p>
            <label for="my_source"><strong>منبع:</strong></label>
            <input
                type="text"
                id="my_source"
                name="my_source"
                value="<?php echo esc_attr( $source ); ?>"
                class="widefat" />
        </p>
        <p>
            <label for="my_source_url"><strong>نشانی منبع:</strong></label>
            <input
                type="url"
                id="my_source_url"
                name="my_source_url"
                value="<?php echo esc_url( $url ); ?>"
                class="widefat"
                dir="ltr" />
        </p>
    </div>
    <?php
}

سه نکتهٔ حیاتی: یک — wp_nonce_field: الزامی برای هر فرم؛ بدون آن، خطر CSRF. دو — esc_attr و esc_url: روی تمام مقادیر خروجی. سه — پیشوند _ در نام متادیتا: مخفی‌کردن از باکس Custom Fields پیش‌فرض وردپرس. یک قاعدهٔ نام‌گذاری: اگر می‌خواهید فیلد در باکس پیش‌فرض نمایش داده نشود، حتماً از _ ابتدای نام استفاده کنید. راهنمای پاک‌سازی و escape در PHP امن در وردپرس و پاک‌سازی داده‌ها.

ذخیره‌سازی امن متادیتا

ذخیره‌سازی متادیتا، روی هوک save_post انجام می‌شود. اسکلت استاندارد:

function my_plugin_save_source( $post_id ) {
    // ۱. نانس
    if ( ! isset( $_POST['my_plugin_source_nonce'] ) ) {
        return;
    }
    if ( ! wp_verify_nonce( $_POST['my_plugin_source_nonce'], 'my_plugin_save_source' ) ) {
        return;
    }

    // ۲. autosave
    if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
        return;
    }

    // ۳. دسترسی کاربر
    if ( ! current_user_can( 'edit_post', $post_id ) ) {
        return;
    }

    // ۴. پاک‌سازی و ذخیره
    if ( isset( $_POST['my_source'] ) ) {
        update_post_meta(
            $post_id,
            '_my_source',
            sanitize_text_field( wp_unslash( $_POST['my_source'] ) )
        );
    }

    if ( isset( $_POST['my_source_url'] ) ) {
        update_post_meta(
            $post_id,
            '_my_source_url',
            esc_url_raw( wp_unslash( $_POST['my_source_url'] ) )
        );
    }
}
add_action( 'save_post', 'my_plugin_save_source' );

چهار بررسی که همیشه باید حاضر باشند: یک — nonce: جلوگیری از CSRF. دو — autosave: جلوگیری از ذخیره در زمان درج خودکار. سه — capability: جلوگیری از دسترسی غیرمجاز. چهار — sanitize: پاک‌سازی ورودی بسته به نوع فیلد. یک نکتهٔ کم‌دیده‌شده: همیشه از wp_unslash روی $_POST استفاده کنید. وردپرس در حالت پیش‌فرض، داده‌های $_POST را با addslashes تغییر می‌دهد و بدون wp_unslash، کوتیشن‌ها و بک‌اسلش‌ها به‌درستی ذخیره نمی‌شوند. راهنمای کامل در اعتبارسنجی داده‌ها، نانس وردپرس، و امنیت پروژهٔ وردپرس.

نانس و جلوگیری از CSRF

نانس، مکانیزم رسمی وردپرس برای جلوگیری از حملات CSRF است. حملات CSRF، کاربر لاگین‌شده را فریب می‌دهند تا درخواست ناخواسته‌ای به سایت بفرستد. با nonce، وردپرس مطمئن می‌شود که درخواست از منبع معتبر آمده است. سه نقطهٔ استفاده: یک — فرم‌های متاباکس. دو — دکمه‌های AJAX. سه — لینک‌های حذف/فعال‌سازی. الگوی AJAX:

// در رندر متاباکس
<button type="button" data-nonce="<?php echo esc_attr( wp_create_nonce( 'my_ajax_action' ) ); ?>">دکمه</button>

// در پردازش AJAX
function my_ajax_handler() {
    check_ajax_referer( 'my_ajax_action', 'nonce' );
    // پردازش
    wp_send_json_success( $data );
}
add_action( 'wp_ajax_my_action', 'my_ajax_handler' );

راهنمای کامل در نانس وردپرس و پیاده‌سازی نانس در فرم‌ها. یک آسیب‌پذیری شایع که در پرونده‌های امنیتی دیده‌ام: نانس‌هایی که با عمر بسیار طولانی یا صفر تنظیم شده بودند؛ این کار، محافظت را کم‌اثر می‌کند. نانس پیش‌فرض وردپرس، ۱۲ ساعت عمر دارد که برای اکثر موارد کافی است.

انواع فیلد: متن، انتخاب، تصویر، تکرارشونده

پنج نوع فیلد پرکاربرد در متاباکس، با الگوی هر یک:

یک — فیلد متنی: الگو در بخش قبل. همیشه sanitize_text_field.

دو — فیلد انتخابی (Select):

$status = get_post_meta( $post->ID, '_my_status', true );
?>
<select name="my_status">
    <option value="draft"  <?php selected( $status, 'draft' ); ?>>پیش‌نویس</option>
    <option value="review" <?php selected( $status, 'review' ); ?>>بازبینی</option>
    <option value="live"   <?php selected( $status, 'live' ); ?>>منتشرشده</option>
</select>

پاک‌سازی: با sanitize_key یا چک کردن در فهرست مقادیر مجاز.

سه — چک‌باکس:

$featured = get_post_meta( $post->ID, '_my_featured', true );
?>
<label>
    <input type="checkbox" name="my_featured" value="1" <?php checked( $featured, '1' ); ?> />
    نمایش در اسلایدر صفحهٔ اصلی
</label>

پاک‌سازی: با isset بررسی و مقدار ۱ یا ۰ ذخیره شود.

چهار — فیلد تصویر: با استفاده از media uploader وردپرس:

$image_id  = get_post_meta( $post->ID, '_my_image_id', true );
$image_url = $image_id ? wp_get_attachment_image_url( $image_id, 'medium' ) : '';
?>
<div class="my-image-field">
    <input type="hidden" name="my_image_id" id="my_image_id" value="<?php echo esc_attr( $image_id ); ?>" />
    <button type="button" class="button my-select-image">انتخاب تصویر</button>
    <?php if ( $image_url ) : ?>
        <img src="<?php echo esc_url( $image_url ); ?>" style="max-width:200px;display:block;margin-top:8px;" />
    <?php endif; ?>
</div>

پاک‌سازی: با absint روی شناسهٔ تصویر. بارگذاری media uploader نیاز به enqueue اسکریپت wp.media در پیشخوان دارد: wp_enqueue_media().

پنج — فیلد تکرارشونده (Repeater): نیاز به JavaScript دارد. الگوی ساده:

<div id="my-repeater">
    <?php
    $items = get_post_meta( $post->ID, '_my_items', true );
    $items = is_array( $items ) ? $items : array();
    foreach ( $items as $index => $item ) :
    ?>
        <div class="repeater-row">
            <input type="text" name="my_items[<?php echo $index; ?>]" value="<?php echo esc_attr( $item ); ?>" />
            <button type="button" class="remove-row">حذف</button>
        </div>
    <?php endforeach; ?>
</div>
<button type="button" id="add-row">افزودن ردیف</button>

پاک‌سازی آرایه در save: با array_map( 'sanitize_text_field', $_POST['my_items'] ) و فیلتر مقادیر خالی. راهنمای دقیق انواع فیلد در ساخت فیلدهای سفارشی.

نمایش متادیتا در front-end

نمایش متادیتا در قالب، سه نکته دارد: یک — استفاده از get_post_meta با ID صحیح. در حلقه، get_the_ID(). دو — escape خروجی. esc_html، wp_kses_post، esc_url. سه — بررسی خالی بودن. الگو:

$source = get_post_meta( get_the_ID(), '_my_source', true );
if ( ! empty( $source ) ) {
    printf(
        '<p class="source-meta">منبع: %s</p>',
        esc_html( $source )
    );
}

برای نمایش در محل مشخصی از content، از فیلتر the_content استفاده کنید:

add_filter( 'the_content', function( $content ) {
    if ( ! is_singular( 'post' ) ) {
        return $content;
    }
    $source = get_post_meta( get_the_ID(), '_my_source', true );
    if ( ! empty( $source ) ) {
        $content .= sprintf(
            '<p class="source-meta">منبع: %s</p>',
            esc_html( $source )
        );
    }
    return $content;
} );

راهنمای هوک the_content در هوک‌های محتوای نوشته. نکته: فیلتر the_content ممکن است در آرشیو و ویجت هم اجرا شود؛ بنابراین بررسی is_singular لازم است.

ساختار کلاس‌محور متاباکس

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

class My_Plugin_Source_Metabox {
    const NONCE_ACTION = 'my_plugin_save_source';
    const NONCE_NAME   = 'my_plugin_source_nonce';

    public static function init() {
        add_action( 'add_meta_boxes', array( __CLASS__, 'register' ) );
        add_action( 'save_post', array( __CLASS__, 'save' ) );
    }

    public static function register() {
        add_meta_box(
            'my_source_box',
            'اطلاعات منبع',
            array( __CLASS__, 'render' ),
            array( 'post', 'article' ),
            'normal',
            'high'
        );
    }

    public static function render( $post ) {
        wp_nonce_field( self::NONCE_ACTION, self::NONCE_NAME );
        $source = get_post_meta( $post->ID, '_my_source', true );
        include plugin_dir_path( __FILE__ ) . 'views/source-metabox.php';
    }

    public static function save( $post_id ) {
        if ( ! isset( $_POST[ self::NONCE_NAME ] ) ) {
            return;
        }
        if ( ! wp_verify_nonce( $_POST[ self::NONCE_NAME ], self::NONCE_ACTION ) ) {
            return;
        }
        if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
            return;
        }
        if ( ! current_user_can( 'edit_post', $post_id ) ) {
            return;
        }

        if ( isset( $_POST['my_source'] ) ) {
            update_post_meta(
                $post_id,
                '_my_source',
                sanitize_text_field( wp_unslash( $_POST['my_source'] ) )
            );
        }
    }
}
My_Plugin_Source_Metabox::init();

مزیت این ساختار: تمام منطق متاباکس در یک نقطه، نام‌گذاری‌های بدون تعارض، و امکان تست. الگوی کامل در ساختار فایل‌های افزونهٔ استاندارد و استانداردهای کدنویسی وردپرس. جداسازی فایل رندر به پوشهٔ views/، خوانایی کد را بالا می‌برد.

الگوهای پیشرفته

سه الگوی پیشرفته در متاباکس، برای پروژه‌های واقعی: یک — Conditional Metabox. متاباکسی که فقط برای دسته یا وضعیت مشخصی از محتوا نمایش داده می‌شود:

function my_conditional_metabox() {
    global $post;
    if ( ! $post ) return;

    // فقط برای نوشته‌های دستهٔ «خبر»
    if ( ! has_category( 'news', $post ) ) {
        return;
    }

    add_meta_box( ... );
}

دو — Multiple Contexts. برای متاباکسی که در مکان‌های مختلف صفحه نمایش داده می‌شود، از contextهای مختلف استفاده کنید. در side، فیلدهای کوتاه؛ در normal، فیلدهای بلند. سه — Gutenberg-Compatible. اگر سایت شما از گوتنبرگ استفاده می‌کند، متاباکس‌ها هنوز کار می‌کنند ولی تجربهٔ کاربری بهتری با PluginDocumentSettingPanel است. راهنمای بلاک گوتنبرگ در ساخت بلوک سفارشی گوتنبرگ و گوتنبرگ و آیندهٔ ویرایش محتوا. یک نکتهٔ ساختاری از پروژه‌های بزرگ: اگر متاباکس شما بیش از ۵ فیلد دارد، آن را به گروه‌های منطقی تقسیم کنید و از tab داخلی یا section استفاده کنید. این کار، تجربهٔ کاربر را به‌شدت بهتر می‌کند. الگوی کدنویسی حرفه‌ای در بهینه‌سازی کد وردپرس و تست و دیباگ پروژه‌های وردپرس آمده است.

اشتباهات رایج

متاباکس، یکی از پرکاربردترین ابزارهای توسعه‌دهندهٔ وردپرس است. مسیر ساخت آن، هفت گام دارد: ثبت با add_meta_box، رندر با callback، ذخیره با save_post، رعایت چهار بررسی امنیتی (nonce، autosave، capability، sanitize)، انتخاب صحیح context و priority، و در پروژه‌های جدی، ساختار کلاس‌محور. اگر امروز یک کار در این مسیر انجام می‌دهید: یک متاباکس ساده برای افزودن فیلد «منبع» به نوشته‌ها بسازید و آن را در front-end نمایش دهید. همین اولین تجربه، پایه‌ای برای پیاده‌سازی فیلدهای پیشرفته‌تر می‌شود. اگر تجربه‌ای از یک متاباکس اختصاصی دارید که در بلندمدت مفید یا پرمشکل بوده، در دیدگاه‌ها بنویسید — همان گزارش‌های واقعی، این راهنما را دقیق‌تر می‌کند. 🧩