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

User Meta چیست و چه زمانی لازم است؟

User Meta، مکانیزم وردپرس برای ذخیرهٔ دادهٔ اضافی روی یک کاربر است. این داده در جدول wp_usermeta ذخیره می‌شود و از طریق توابع اختصاصی وردپرس قابل مدیریت است. وردپرس از همان ابتدا چند متای پیش‌فرض دارد: first_name، last_name، nickname، description، wp_capabilities (نقش)، wp_user_level، admin_color، session_tokens و چند مورد دیگر. چهار سناریوی اصلی نیاز به User Meta: یک — اطلاعات هویتی اضافه. مثال: کد ملی، شمارهٔ شناسنامه، کد پرسنلی. دو — اطلاعات تماس شخصی‌سازی‌شده. مثال: واتس‌اپ، لینکدین، شمارهٔ دوم. سه — داده‌های کسب‌وکاری. مثال: سطح عضویت، امتیاز، تاریخ خریدهای اخیر. چهار — تنظیمات شخصی کاربر. مثال: زبان ترجیحی، حالت تیره، نوتیفیکیشن. مقایسهٔ ساختار User Meta با متادیتای پست در کار با متاباکس‌ها و توابع پایه در توابع وردپرس برای کاربران آمده است.

User Meta، لایهٔ داده‌ای است که کاربر وردپرس را از یک حساب ساده به یک پروفایل با هویت دیجیتال تبدیل می‌کند.

افزودن، خواندن، به‌روزرسانی و حذف

چهار عمل اصلی روی User Meta با چهار تابع انجام می‌شود:

// افزودن متا
add_user_meta( $user_id, '_phone', '09123456789', true );

// خواندن متا
$phone = get_user_meta( $user_id, '_phone', true );

// به‌روزرسانی متا
update_user_meta( $user_id, '_phone', '09987654321' );

// حذف متا
delete_user_meta( $user_id, '_phone' );

نکتهٔ مهم در پارامتر چهارم add_user_meta (و همچنین update_user_meta): یک — اگر true باشد: کلید یکتا در نظر گرفته می‌شود و مقدار قبلی جایگزین می‌شود. دو — اگر false باشد: می‌توانید چند مقدار با یک کلید ذخیره کنید. برای اکثر موارد، true انتخاب درستی است. برای داده‌های چندمقداری (مثل لیست علاقه‌مندی‌ها)، از آرایه استفاده کنید نه چند مقدار جداگانه. سه تذکر در استفاده: یک — پیشوند _: متاهایی که با _ شروع می‌شوند، از نمایش در فرم پروفایل پیش‌فرض وردپرس مخفی می‌شوند. دو — همیشه get_user_meta با پارامتر سوم true: اگر نگذارید، آرایه برمی‌گرداند. سه — کاربر نامعتبر: توابع، در صورت نبود کاربر، false برمی‌گردانند. راهنمای کامل توابع پایه در توابع وردپرس برای داده‌های کاربر و توابع متادیتا.

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

نمایش و ذخیرهٔ User Meta در فرم پروفایل پیشخوان، با سه هوک انجام می‌شود:

// نمایش فیلد در فرم پروفایل کاربر
function my_plugin_user_profile_fields( $user ) {
    ?>
    <h2>اطلاعات تماس اضافه</h2>
    <table class="form-table">
        <tr>
            <th><label for="my_phone">شمارهٔ موبایل</label></th>
            <td>
                <input
                    type="tel"
                    name="my_phone"
                    id="my_phone"
                    value="<?php echo esc_attr( get_user_meta( $user->ID, '_my_phone', true ) ); ?>"
                    class="regular-text"
                    dir="ltr" />
                <p class="description">برای دریافت پیامک‌های اطلاع‌رسانی</p>
            </td>
        </tr>
    </table>
    <?php
}
add_action( 'show_user_profile', 'my_plugin_user_profile_fields' );
add_action( 'edit_user_profile', 'my_plugin_user_profile_fields' );

// ذخیرهٔ فیلد
function my_plugin_save_user_profile_fields( $user_id ) {
    if ( ! current_user_can( 'edit_user', $user_id ) ) {
        return false;
    }

    if ( isset( $_POST['my_phone'] ) ) {
        update_user_meta(
            $user_id,
            '_my_phone',
            sanitize_text_field( wp_unslash( $_POST['my_phone'] ) )
        );
    }
}
add_action( 'personal_options_update', 'my_plugin_save_user_profile_fields' );
add_action( 'edit_user_profile_update', 'my_plugin_save_user_profile_fields' );

سه نکتهٔ حیاتی: یک — دو هوک برای نمایش: show_user_profile برای پروفایل کاربر خودش، edit_user_profile برای ویرایش از طرف ادمین. برای نمایش هر دو، هر دو را ثبت کنید. دو — دو هوک برای ذخیره: personal_options_update و edit_user_profile_update. سه — بررسی دسترسی: current_user_can( 'edit_user', $user_id ) الزامی است تا کاربر عادی نتواند پروفایل دیگری را تغییر دهد. راهنمای امنیت در PHP امن در وردپرس و پاک‌سازی داده‌ها.

فیلد در فرم ثبت‌نام

برای افزودن فیلد در فرم ثبت‌نام، از هوک register_form برای نمایش و user_register برای ذخیره استفاده کنید:

// نمایش فیلد در فرم ثبت‌نام
function my_plugin_register_form_field() {
    ?>
    <p>
        <label for="my_phone">شمارهٔ موبایل<br />
            <input
                type="tel"
                name="my_phone"
                id="my_phone"
                class="input"
                value="<?php echo isset( $_POST['my_phone'] ) ? esc_attr( sanitize_text_field( wp_unslash( $_POST['my_phone'] ) ) ) : ''; ?>"
                size="25"
                dir="ltr" />
        </label>
    </p>
    <?php
}
add_action( 'register_form', 'my_plugin_register_form_field' );

// اعتبارسنجی
function my_plugin_register_validation( $errors, $sanitized_user_login, $user_email ) {
    if ( empty( $_POST['my_phone'] ) ) {
        $errors->add( 'my_phone_error', 'لطفاً شمارهٔ موبایل خود را وارد کنید.' );
    } elseif ( ! preg_match( '/^09[0-9]{9}$/', $_POST['my_phone'] ) ) {
        $errors->add( 'my_phone_format_error', 'شمارهٔ موبایل معتبر نیست.' );
    }
    return $errors;
}
add_filter( 'registration_errors', 'my_plugin_register_validation', 10, 3 );

// ذخیره هنگام ثبت‌نام
function my_plugin_save_register_field( $user_id ) {
    if ( isset( $_POST['my_phone'] ) ) {
        update_user_meta(
            $user_id,
            '_my_phone',
            sanitize_text_field( wp_unslash( $_POST['my_phone'] ) )
        );
    }
}
add_action( 'user_register', 'my_plugin_save_register_field' );

نکتهٔ مهم در فرم ثبت‌نام: ورودی کاربر ناشناس است، بنابراین اعتبارسنجی سخت‌گیرانه لازم است. حتماً از registration_errors برای اعتبارسنجی استفاده کنید. راهنمای فرم ثبت‌نام در هوک‌های ورود و ثبت‌نام و فعال‌سازی 2FA برای کاربران.

نمایش در front-end و پنل کاربری

نمایش متادیتای کاربر در front-end، سه نکته دارد: یک — دسترسی به داده. در پنل کاربری خودش، از get_current_user_id(). برای نمایش پروفایل کاربران دیگر (اگر مجاز است)، از ID کاربر استفاده کنید. دو — escape خروجی. esc_html، esc_url، esc_attr بسته به زمینه. سه — بررسی خالی بودن. الگو:

$user_id = get_current_user_id();
$phone   = get_user_meta( $user_id, '_my_phone', true );

if ( ! empty( $phone ) ) {
    printf(
        '<p class="user-phone">شمارهٔ تماس: %s</p>',
        esc_html( $phone )
    );
}

برای نمایش پروفایل عمومی کاربر در فرانت‌اند، از فایل author.php در قالب یا از یک page template اختصاصی استفاده کنید. راهنما در ساختار فایل‌های قالب استاندارد و توابع دادهٔ کاربر. یک نکتهٔ مهم: پیش از نمایش هر دادهٔ خصوصی کاربر در فرانت‌اند، مطمئن شوید که دسترسی برای مشاهده‌کننده مجاز است. مثلاً شماره موبایل، نباید در پروفایل عمومی نمایش داده شود. یک الگوی حرفه‌ای: متاهای عمومی را با پیشوند public_ و متاهای خصوصی را با پیشوند _ نام‌گذاری کنید، و در فرانت‌اند فقط متاهای public_ را نمایش دهید. این تفکیک نام‌گذاری، در بازبینی کد، یک سطل کوچک اما مفید است.

کوئری بر اساس User Meta

پرس‌وجوی کاربران بر اساس متادیتا، با کلاس WP_User_Query:

$args = array(
    'meta_key'     => '_membership_level',
    'meta_value'   => 'gold',
    'meta_compare' => '=',
    'number'       => 20,
    'orderby'      => 'registered',
    'order'        => 'DESC',
);
$query = new WP_User_Query( $args );

if ( ! empty( $query->results ) ) {
    foreach ( $query->results as $user ) {
        printf(
            '<li>%s (%s)</li>',
            esc_html( $user->display_name ),
            esc_html( get_user_meta( $user->ID, '_membership_level', true ) )
        );
    }
}

برای کوئری‌های پیچیده‌تر، از meta_query استفاده کنید:

$args = array(
    'meta_query' => array(
        'relation' => 'AND',
        array(
            'key'     => '_membership_level',
            'value'   => 'gold',
            'compare' => '=',
        ),
        array(
            'key'     => '_last_login',
            'value'   => strtotime( '-30 days' ),
            'compare' => '>',
            'type'    => 'NUMERIC',
        ),
    ),
    'number' => 50,
);
$query = new WP_User_Query( $args );

نکات بهینه‌سازی: یک — number صریح بگذارید. بدون آن، همهٔ کاربران بارگذاری می‌شوند. دو — count_total را اگر لازم نیست، غیرفعال کنید: با 'count_total' => false. سه — fields را محدود کنید: اگر فقط نام و ایمیل لازم است، 'fields' => array( 'ID', 'display_name', 'user_email' ). راهنمای کامل در توابع وردپرس برای کاربران، توابع کوئری سفارشی، و بهینه‌سازی کوئری‌ها. یک نکتهٔ عملکردی مهم: کوئری روی wp_usermeta در سایت‌های با کاربران زیاد، می‌تواند سنگین شود. اگر تعداد کاربران بالای چند هزار است، کوئری‌های ترکیبی را با care بنویسید و نتایج را با transients کش کنید. الگو در ترنزینت‌ها در وردپرس.

کش و عملکرد

وردپرس به‌طور خودکار متادیتای کاربر را در حافظه کش می‌کند. وقتی یک کاربر با get_userdata یا get_user_by لود می‌شود، تمام متاهای او در یک کوئری گرفته و در حافظه ذخیره می‌شوند. بنابراین فراخوانی‌های بعدی get_user_meta کوئری اضافه نمی‌زنند. سه نکته: یک — ترتیب فراخوانی: ابتدا get_userdata، سپس get_user_meta. دو — cache clear: پس از update_user_meta، کش داخلی به‌طور خودکار به‌روزرسانی می‌شود. سه — multiple user get: برای نمایش لیست کاربران، ابتدا تمام IDها را بگیرید و با get_users یک‌جا لود کنید. راهنمای بهینه‌سازی در بهینه‌سازی کد وردپرس و بهینه‌سازی کوئری‌ها. یک نکته در سایت‌های با کاربران زیاد: اگر صفحه‌ای نمایش ۱۰۰ کاربر با ۵ متای هرکدام را دارد، بدون optimization، ۵۰۰ کوئری اجرا می‌شود. با get_users و استفاده از cache داخلی، به ۲-۳ کوئری کاهش می‌یابد.

انواع داده و ساختاردهی

User Meta می‌تواند انواع مختلف داده را نگه دارد. چهار الگوی متداول: یک — مقدار تکی (string یا int): با update_user_meta( $id, '_phone', '0912...' ) و خواندن با get_user_meta( $id, '_phone', true ). دو — آرایه: وردپرس آرایه را خودش سریالایز می‌کند:

$interests = array( 'books', 'travel', 'coding' );
update_user_meta( $user_id, '_interests', $interests );

$saved = get_user_meta( $user_id, '_interests', true );
// $saved یک آرایه است
if ( in_array( 'books', $saved, true ) ) {
    // ...
}

سه — مقدار بولی: وردپرس بولی را در دیتابیس به 1 یا رشتهٔ خالی تبدیل می‌کند. بنابراین همیشه به‌عنوان boolean بخوانید:

update_user_meta( $user_id, '_newsletter_optin', true );
$optin = (bool) get_user_meta( $user_id, '_newsletter_optin', true );

چهار — تاریخ: توصیه می‌شود تاریخ را به‌صورت timestamp ذخیره کنید، نه رشته:

update_user_meta( $user_id, '_last_login', time() );
$last = (int) get_user_meta( $user_id, '_last_login', true );
if ( $last ) {
    echo esc_html( date_i18n( 'j F Y', $last ) );
}

نکته: timestamp ذخیره‌شده، امکان کوئری عددی و مقایسه را در WP_User_Query فراهم می‌کند؛ رشته تاریخ، این امکان را محدود می‌کند. راهنمای پاک‌سازی و انواع داده در پاک‌سازی داده‌ها، اعتبارسنجی داده‌ها، و توابع متادیتا.

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

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

class My_Plugin_User_Profile {
    const META_PHONE         = '_my_phone';
    const META_LAST_LOGIN    = '_my_last_login';
    const META_NEWSLETTER    = '_my_newsletter_optin';

    public static function init() {
        add_action( 'show_user_profile', array( __CLASS__, 'render_fields' ) );
        add_action( 'edit_user_profile', array( __CLASS__, 'render_fields' ) );
        add_action( 'personal_options_update', array( __CLASS__, 'save_fields' ) );
        add_action( 'edit_user_profile_update', array( __CLASS__, 'save_fields' ) );
        add_action( 'wp_login', array( __CLASS__, 'record_login' ), 10, 2 );
    }

    public static function render_fields( $user ) {
        include plugin_dir_path( __FILE__ ) . '../admin/views/user-profile.php';
    }

    public static function save_fields( $user_id ) {
        if ( ! current_user_can( 'edit_user', $user_id ) ) {
            return false;
        }
        if ( isset( $_POST['my_phone'] ) ) {
            update_user_meta(
                $user_id,
                self::META_PHONE,
                sanitize_text_field( wp_unslash( $_POST['my_phone'] ) )
            );
        }
        update_user_meta(
            $user_id,
            self::META_NEWSLETTER,
            isset( $_POST['my_newsletter'] ) ? 1 : 0
        );
    }

    public static function record_login( $user_login, $user ) {
        update_user_meta( $user->ID, self::META_LAST_LOGIN, time() );
    }
}
My_Plugin_User_Profile::init();

مزیت این ساختار: تمام منطق User Meta در یک نقطه، ثابت‌های نام‌گذاری بدون تعارض، و امکان تست. الگوهای مشابه در کدنویسی اختصاصی افزونه و ساختار فایل‌های افزونهٔ استاندارد. یک نکتهٔ ساختاری مهم: ثابت‌های نام‌گذاری متا (مثل META_PHONE) را در یک نقطه از پروژه تعریف کنید و همه‌جا از همان‌ها استفاده کنید. این کار، در بازنویسی‌های آینده، جلوگیری از اشتباه تایپی و ناهماهنگی می‌کند. الگوهای استاندارد کد در استانداردهای کدنویسی وردپرس و پیاده‌سازی استانداردها آمده است. در پروژه‌های سازمانی، همین یک الگو، تفاوت بین کدی که سه سال بعد قابل نگهداری است و کدی که دو ماه بعد رها می‌شود را می‌سازد.

امنیت و محدودیت دسترسی

پنج قاعدهٔ الزامی در کار با User Meta: یک — بررسی دسترسی: در ذخیره، همیشه current_user_can( 'edit_user', $user_id ). دو — پاک‌سازی ورودی: هر فیلد بسته به نوع خود. سه — escape خروجی: در نمایش، همیشه esc_html، esc_attr، esc_url. چهار — نانس در فرم‌های پروفایل: فرم پروفایل وردپرس، نانس دارد؛ اگر فرم سفارشی می‌سازید، نانس اضافه کنید. پنج — محدودسازی فیلدهای حساس: فیلدهایی مثل نقش کاربر (wp_capabilities) نباید از طریق فرم ساده تغییر کنند. راهنمای کامل در PHP امن در وردپرس، امنیت وردپرس برای مبتدیان، و امنیت پروژه وردپرس. یک آسیب‌پذیری شایع که در پرونده‌های امنیتی دیده‌ام: فرم پروفایلی که current_user_can( 'edit_user', $user_id ) را نداشت — نتیجه: هر کاربر لاگین‌شده می‌توانست با ارسال یک درخواست ساختگی، متادیتای کاربران دیگر را تغییر دهد. هیچ فرم پروفایلی، از این بررسی معاف نیست.

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

سه الگوی پیشرفته در کار با User Meta، برای پروژه‌های واقعی: یک — User Meta as Feature Toggle. فعال‌سازی قابلیت‌های مختلف برای کاربران مختلف:

function my_plugin_user_has_feature( $user_id, $feature ) {
    $features = get_user_meta( $user_id, '_my_features', true );
    if ( ! is_array( $features ) ) {
        return false;
    }
    return in_array( $feature, $features, true );
}

// استفاده در front-end
if ( my_plugin_user_has_feature( get_current_user_id(), 'download_files' ) ) {
    // نمایش لینک دانلود
}

دو — User Meta as Tier. برای سیستم عضویت یا اشتراک با سطوح مختلف:

function my_plugin_get_user_tier( $user_id ) {
    $tier = get_user_meta( $user_id, '_membership_tier', true );
    return $tier ?: 'free';
}

function my_plugin_user_can_access( $user_id, $required_tier ) {
    $tiers = array( 'free' => 0, 'silver' => 1, 'gold' => 2, 'platinum' => 3 );
    $user_tier = my_plugin_get_user_tier( $user_id );
    return ( $tiers[ $user_tier ] ?? 0 ) >= ( $tiers[ $required_tier ] ?? 0 );
}

سه — User Meta as Audit Log. برای ثبت رویدادهای کاربر:

function my_plugin_log_user_action( $user_id, $action, $context = array() ) {
    $log = get_user_meta( $user_id, '_my_action_log', true );
    if ( ! is_array( $log ) ) {
        $log = array();
    }
    $log[] = array(
        'action'    => $action,
        'context'   => $context,
        'timestamp' => time(),
    );
    // نگه‌داری فقط ۵۰ رکورد آخر
    $log = array_slice( $log, -50 );
    update_user_meta( $user_id, '_my_action_log', $log );
}

نکته: لاگ‌های حجیم را در دیتابیس اختصاصی نگه دارید، نه در User Meta. الگوی مشابه برای لاگ در ترنزینت‌ها. یک الگوی مهم دیگر: برای داده‌های حساس (مثل توکن‌های API شخصی)، به‌جای ذخیره در User Meta، از رمزنگاری استفاده کنید. راهنما در امنیت دیتابیس وردپرس. یک الگوی تکمیلی برای پروژه‌های با کاربران زیاد: پایش آمار استفاده از متا — کدام کلیدها پرمصرف‌اند و کدام فراموش‌شده — ماهانه یک بار، حجم جدول wp_usermeta را بررسی کنید. کلیدهای بی‌استفاده را حذف کنید. این عادت، در تجربهٔ من، اندازهٔ جدول را در سال اول نصف کرده است.

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

کار با User Meta، مسیر روشنی دارد: افزودن و خواندن با چهار تابع اصلی، نمایش در فرم پروفایل و ثبت‌نام با هوک‌های اختصاصی، کوئری با WP_User_Query، کش داخلی وردپرس، انتخاب نوع دادهٔ درست، ساختار کلاس‌محور، و رعایت امنیت در هر دو نقطهٔ ورودی و خروجی. اگر امروز یک کار در این مسیر انجام می‌دهید: به جدول wp_usermeta نگاه کنید و ببینید آیا کلیدهای بی‌استفاده در آن جمع شده‌اند؛ پاک‌سازی آن‌ها، هم اندازهٔ دیتابیس را کم می‌کند و هم کوئری‌های کاربر را سریع‌تر. اگر تجربه‌ای از یک فیلد User Meta دارید که در بلندمدت مفید یا پرمشکل بوده — به‌ویژه در سایت‌های با کاربران زیاد — در دیدگاه‌ها بنویسید؛ همان گزارش‌های واقعی، این راهنما را دقیق‌تر می‌کند. 👤