زمستان دو سال پیش بود که یک مشتری با صدای نگران زنگ زد: «قسمتی از صفحه اصلی سایت ما که مخصوص اعضا بود، برای همه نمایش داده می‌شود.» وارد پیشخوان شدم و کد را باز کردم. مشکل در یک خط ساده بود: توسعه‌دهنده قبلی، محتوای ویژه اعضا را داخل یک شرط if گذاشته بود، اما شرط را اشتباه نوشته بود. به‌جای بررسی وضعیت ورود، یک فیلد متادیتا را چک کرده بود که برای همه کاربران وجود داشت. نتیجه این خطای کوچک، افشای محتوای اختصاصی به مدت دو ماه بود. آن روز به خودم قول دادم که تسلط بر توابع بررسی وضعیت ورود را در اولویت هر پروژه‌ای قرار دهم؛ چون این توابع، مرز بین محتوای عمومی و خصوصی هستند و یک اشتباه کوچک، می‌تواند هزینه سنگینی داشته باشد.

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

چرا بررسی وضعیت ورود، پایه امنیت محتواست؟

در وردپرس، دو نوع محتوا داریم: عمومی و خصوصی. مرز بین این دو، دقیقاً از همان‌جایی می‌گذرد که کد شما بررسی می‌کند کاربر «لاگین است یا نه». سه دلیل بنیادین که چرا این توابع اهمیت حیاتی دارند:

  • افشای محتوا: اگر شرط بررسی وضعیت ورود را اشتباه بنویسید، محتوای خصوصی برای همه قابل مشاهده می‌شود. تجربه‌ام: سایت‌های آموزشی و فروشگاهی، بیشترین آسیب را از این اشتباه دیده‌اند.
  • رفتار نادرست: منو، دکمه، فرم و پیام‌ها باید برای کاربر لاگین‌شده و مهمان، متفاوت باشند. این تفکیک، بخشی از تجربه کاربری حرفه‌ای است.
  • امنیت لایه‌ای: بررسی وضعیت ورود، تنها لایه اول است؛ اما اگر همین لایه اول نباشد، لایه‌های بعدی (بررسی نقش، بررسی نانس، بررسی capability) هم بی‌اثر می‌شوند.

راهنمای کلی این بحث را در توابع بررسی وضعیت ورود، توابع نقش و دسترسی، و PHP امن در وردپرس آورده‌ام.

بررسی وضعیت ورود، دروازه ورود به دنیای محتوای شخصی‌سازی‌شده است؛ اگر این دروازه اشتباه باشد، همه چیز بعد از آن هم بی‌ارزش می‌شود.

توابع اصلی بررسی وضعیت ورود

وردپرس مجموعه‌ای از توابع اختصاصی برای بررسی وضعیت ورود کاربر ارائه می‌دهد. مهم‌ترین آنها:

is_user_logged_in();              // آیا کاربر لاگین کرده؟
get_current_user_id();            // شناسهٔ کاربر جاری یا 0
wp_get_current_user();            // شیء WP_User کاربر جاری
get_currentuserinfo();            // منسوخ، استفاده نکنید
is_admin();                        // آیا در پیشخوان هستیم؟ (نه برای بررسی وضعیت ورود)
wp_get_current_user()->exists();  // بررسی وجود کاربر

نکته: is_user_logged_in سریع‌ترین و استانداردترین تابع برای بررسی وضعیت ورود است. این تابع، در واقع یک wrapper روی wp_get_current_user است و مقدار بولی برمی‌گرداند. کاربردش در شرط‌های ساده:

if ( is_user_logged_in() ) {
    echo "خوش آمدید، " . esc_html( wp_get_current_user()->display_name );
} else {
    printf(
        "<a href="%s">لطفاً وارد شوید</a>",
        esc_url( wp_login_url( get_permalink() ) )
    );
}

در این مثال، wp_login_url( get_permalink() ) کاربر را بعد از ورود به همان صفحه برمی‌گرداند — یک جزئیات کوچک که در تجربه کاربری، تفاوت محسوسی می‌سازد. راهنمای کامل در توابع داده‌های کاربر و توابع کاربران.

شرطی‌سازی محتوا بر اساس وضعیت ورود

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

if ( is_user_logged_in() ) {
    // محتوای اختصاصی اعضا
    the_content();
} else {
    // پیشنهاد ثبت‌نام
    printf(
        "<div class="membership-cta"><p>برای مشاهده این محتوا، <a href="%s">وارد شوید</a>.</p></div>",
        esc_url( wp_login_url( get_permalink() ) )
    );
}

نکته مهم: در این الگو، the_content فقط برای اعضا نمایش داده می‌شود. اما اگر محتوای شما در دیتابیس عمومی است، کاربر می‌تواند از طریق RSS یا API به آن دسترسی پیدا کند. راه‌حل کامل، فیلتر the_content در سطح API و RSS است — که در هوک‌های محتوای نوشته توضیح داده‌ام.

یک الگوی حرفه‌ای‌تر: نمایش بخش اول محتوا به همه، بخش دوم فقط به اعضا. این کار با استفاده از <!--more--> و شرطی‌سازی پیشرفته ممکن است:

if ( is_user_logged_in() ) {
    the_content();
} else {
    $content = get_the_content();
    $parts = explode( "<!--more-->", $content );
    echo wp_kses_post( $parts[0] );
    printf(
        "<p><a href="%s">ادامه مطلب برای اعضا</a></p>",
        esc_url( wp_login_url( get_permalink() ) )
    );
}

این الگو، برای سایت‌های آموزشی و مجلات اشتراکی بسیار مؤثر است. راهنمای تفصیلی در سئوی درون‌صفحه و ساخت فیلدهای سفارشی.

ریدایرکت کاربران بر اساس وضعیت ورود

گاهی نیاز داریم کاربران مهمان را به صفحه ورود هدایت کنیم. بهترین مکان برای این کار، هوک template_redirect است:

add_action( "template_redirect", function() {
    if ( is_singular( "post" ) && in_category( "members-only" ) && ! is_user_logged_in() ) {
        wp_safe_redirect( wp_login_url( get_permalink() ) );
        exit;
    }
} );

سه نکته مهم: یک — wp_safe_redirect نه wp_redirect: تابع wp_safe_redirect فقط به دامنه‌های معتبر ریدایرکت می‌کند و از Open Redirect جلوگیری می‌کند. دو — exit بعد از ریدایرکت: بدون exit، ادامه اجرای کد اتفاق می‌افتد. سه — template_redirect نه init: در init، توابع شرطی مثل is_singular کار نمی‌کنند.

راهنمای کامل ریدایرکت در افزونه‌های ریدایرکت و رفع حلقه ریدایرکت.

نمایش منو و نوار بر اساس وضعیت ورود

یکی از پرتکرارترین نیازها: نمایش آیتم «پنل کاربری» فقط برای اعضا و «ورود / ثبت‌نام» برای مهمان‌ها:

add_filter( "wp_nav_menu_items", function( $items, $args ) {
    if ( "primary" !== $args->theme_location ) {
        return $items;
    }
    
    if ( is_user_logged_in() ) {
        $items .= sprintf(
            "<li class="menu-item"><a href="%s">پنل کاربری</a></li>",
            esc_url( admin_url( "profile.php" ) )
        );
        $items .= sprintf(
            "<li class="menu-item"><a href="%s">خروج</a></li>",
            esc_url( wp_logout_url( home_url() ) )
        );
    } else {
        $items .= sprintf(
            "<li class="menu-item"><a href="%s">ورود</a></li>",
            esc_url( wp_login_url() )
        );
    }
    return $items;
}, 10, 2 );

این الگو، تجربه کاربری سایت را حرفه‌ای‌تر می‌کند. نکته امنیتی مهم: wp_logout_url باید همیشه با نانس و URL بازگشت معتبر استفاده شود. راهنمای کامل منو در توابع منوی وردپرس و ساختار فایل‌های قالب استاندارد.

شخصی‌سازی پیام‌ها و دکمه‌ها

پیام خوش‌آمد، دکمه سبد خرید، و لینک دانلود، همه باید بر اساس وضعیت ورود تغییر کنند:

if ( is_user_logged_in() ) {
    $user = wp_get_current_user();
    printf(
        "<div class="welcome-bar">سلام <strong>%s</strong>، آخرین ورود شما: %s</div>",
        esc_html( $user->display_name ),
        esc_html( get_user_meta( $user->ID, "_last_login", true ) ?: "اولین بار" )
    );
}

نکته: آخرین ورود کاربر را می‌توانید با هوک wp_login در متادیتا ذخیره کنید. راهنمای متادیتا در کار با User Meta و توابع متادیتا.

add_action( "wp_login", function( $user_login, $user ) {
    update_user_meta( $user->ID, "_last_login", current_time( "mysql" ) );
}, 10, 2 );

بررسی وضعیت ورود در افزونه‌ها و AJAX

در افزونه‌ها و endpointهای AJAX، بررسی وضعیت ورود جدی‌تر است. یک endpoint AJAX که داده حساس برمی‌گرداند، باید در چند لایه محافظت شود:

add_action( "wp_ajax_myplugin_get_private_data", "myplugin_ajax_handler" );
// نکته: wp_ajax_nopriv_myplugin_get_private_data ثبت نشده — مهمان‌ها دسترسی ندارند

function myplugin_ajax_handler() {
    check_ajax_referer( "myplugin_nonce", "nonce" );
    
    if ( ! is_user_logged_in() ) {
        wp_send_json_error( array( "message" => "دسترسی غیرمجاز" ), 401 );
    }
    
    $user_id = get_current_user_id();
    if ( ! current_user_can( "read" ) ) {
        wp_send_json_error( array( "message" => "دسترسی کافی ندارید" ), 403 );
    }
    
    // پردازش داده
    wp_send_json_success( $data );
}

نکته مهم: هوک wp_ajax_nopriv_myplugin_get_private_data عمداً ثبت نشده — یعنی این endpoint فقط برای کاربران لاگین‌شده فعال است. راهنمای امنیت AJAX در نانس وردپرس و پیاده‌سازی نانس در فرم‌ها.

بررسی وضعیت ورود در REST API

در REST API وردپرس، بررسی وضعیت ورود با permission_callback انجام می‌شود:

register_rest_route( "myplugin/v1", "/private", array(
    "methods"             => "GET",
    "callback"            => "myplugin_rest_handler",
    "permission_callback" => function() {
        return is_user_logged_in() && current_user_can( "read" );
    },
) );

نکته: permission_callback خالی یا __return_true بدون دلیل، خطرناک است. راهنمای کامل در ساخت API اختصاصی و REST API وردپرس.

وضعیت ورود در قالب‌های چایلد

در چایلد تم، می‌توانید نمایش بخش‌های قالب را بر اساس وضعیت ورود تغییر دهید. مثلاً نمایش دکمه خرید در فوتر فقط برای اعضا:

// در functions.php چایلد
add_action( "wp_footer", function() {
    if ( is_user_logged_in() ) {
        echo "<div class="sticky-cta"><a href="" . esc_url( wc_get_cart_url() ) . "">سبد خرید</a></div>";
    }
} );

نکته: در چایلد تم، همیشه از پیشوند یکتا برای توابع استفاده کنید تا با قالب والد تعارض نکنید. راهنمای چایلد تم در قالب چایلد چیست و توسعه با چایلد تم.

خطاهای رایج و راه‌حل

  • اشتباه گرفتن is_user_logged_in با is_admin: is_admin بررسی می‌کند آیا در پیشخوان هستیم، نه آیا کاربر لاگین است. برای بررسی وضعیت ورود، is_user_logged_in درست است.
  • استفاده از get_currentuserinfo: منسوخ و خطرناک در PHP 8. راهنمای خطای Deprecated.
  • نبود exit بعد از ریدایرکت: باعث ادامه اجرای کد و فاجعه می‌شود. راهنمای رفع حلقه ریدایرکت.
  • نبود wp_safe_redirect: خطر Open Redirect. راهنمای افزونه‌های ریدایرکت.
  • نبود permission_callback در REST API: افشای داده. راهنمای API اختصاصی.
  • نبود check_ajax_referer در AJAX: خطر CSRF. راهنمای حمله CSRF.
  • افشای محتوای خصوصی در RSS: فیلتر the_content برای RSS فعال نیست. راهنمای هوک‌های محتوای نوشته.
  • نبود escape در نمایش فیلدهای کاربر: خطر XSS. راهنمای پاک‌سازی داده‌ها.
  • فراموش کردن شرط‌سازی در قالب‌های چایلد: فایل چایلد بدون شرط، محتوای خصوصی را افشا می‌کند. راهنمای چایلد تم.
  • نبود تست با چند نقش: رفتار سایت با نقش‌های مختلف باید تست شود. راهنمای تست و دیباگ.

تست امنیت بررسی وضعیت ورود

پس از پیاده‌سازی، سه تست ضروری:

  1. تست با مرورگر ناشناس (Incognito): بدون لاگین، صفحه مورد نظر را باز کنید. آیا محتوای خصوصی نمایش داده می‌شود؟
  2. تست با کاربر نقش پایین (Subscriber): با یک کاربر سطح پایین وارد شوید. آیا محتوای مخصوص مدیران را می‌بینید؟
  3. تست مستقیم URL: اگر محتوای خصوصی در یک برگه با URL مشخص است، بدون لاگین مستقیم به آن URL بروید. آیا ریدایرکت می‌شوید؟

راهنمای کامل تست در تست امنیت وب‌سایت و تست قالب وردپرس. در تجربه‌ام، بزرگ‌ترین آسیب‌ها از همین تست سوم می‌آید — فراموش کردن مستقیم URL.

جمع‌بندی

بررسی وضعیت ورود کاربر، لایه اول امنیت محتوا و تجربه کاربری است. توابع اصلی is_user_logged_in، get_current_user_id، و wp_get_current_user هستند. سه اصل را در پایان تاکید می‌کنم: اول، همیشه با wp_safe_redirect ریدایرکت کنید. دوم، برای AJAX و REST API لایه‌های check_ajax_referer و permission_callback را اضافه کنید. سوم، تست مستقیم URL را فراموش نکنید.

اگر تجربه‌ای از یک باگ امنیتی یا مشکل نمایشی در بررسی وضعیت ورود دارید که با تابع درست حل شد، در دیدگاه‌ها بنویسید — همان گزارش‌های واقعی، این راهنما را برای توسعه‌دهنده بعدی دقیق‌تر می‌کند. 🔐