تسجيل حضور الفعاليات بـ QR في ووردبريس بدون تطبيق ماسح

موبايل بشاشة خضراء يقرأ تذكرة رمزها يتحول إلى مكعبات، يمثل تسجيل حضور QR في ووردبريس

ورشة فيها 150 واحد، وعلى الباب اتنين متطوعين بيقلّبوا في نفس الورقتين بيدوروا على “محمد أحمد”، وفيه خمسة باسم محمد أحمد. الطابور بيطول، والناس بتتعصب. الحل مش لازم يكون تطبيق يتحمّل على موبايل كل متطوع.

كاميرا أي موبايل حديث تقرأ رمز الاستجابة السريعة (QR Code) وتفتح الرابط الموجود بداخله مباشرة. إذن يكفي أن يكون رمز التذكرة رابطًا إلى موقعك على ووردبريس (WordPress)، ويصبح تسجيل الحضور مجرد فتح صفحة على موبايل المتطوع.

الأكواد التالية PHP عادي يعتمد على دوال ووردبريس، مأخوذة من تجربة كاتب المقال الأصلي أثناء بناء هذه الميزة لإضافة فعاليات. عدّل اسم الجدول ودالة جلب الحجز لتناسب طريقة تخزين الحجوزات عندك.

لا تضع رابط الحجز الخاص في الرمز

معظم أنظمة الحجز ترسل للضيف رابطًا خاصًا لعرض حجزه أو إلغائه، ومن المغري إعادة استخدامه في الرمز. لا تفعل ذلك: الناس تصوّر تذاكرها وتنشرها على فيسبوك أو ترسلها في جروبات واتساب، وأي شخص يمسح الصورة سيحصل على رابط الإلغاء.

الأفضل رمز منفصل لا يفعل إلا شيئًا واحدًا: تسجيل الحضور. يُشتق من رقم الحجز والرمز السري للحجز باستخدام رمز مصادقة الرسالة المُجزّأ (HMAC)، فلا يمكن تخمينه ولا يكشف الرمز السري:

function checkin_code( array $booking ): string {
    $mac = hash_hmac( 'sha256', 'checkin|' . (int) $booking['id'] . '|' . $booking['token'], wp_salt( 'auth' ) );
    return (int) $booking['id'] . '-' . substr( $mac, 0, 24 ); // 96 bits is plenty for this
}

function booking_by_code( string $code ): ?array {
    if ( ! preg_match( '/^(d+)-([a-f0-9]{24})$/', $code, $m ) ) {
        return null;
    }
    $row = get_booking( (int) $m[1] ); // your lookup
    return ( $row && hash_equals( checkin_code( $row ), $code ) ) ? $row : null;
}

لاحظ نقطتين في الكود:

  • hash_equals بدل ===: تستغرق المقارنة الزمن نفسه أيًّا كان موضع الحرف الخاطئ، فلا يستطيع مهاجم استنتاج الرمز من سرعة الرد.
  • البادئة checkin|: تفصل هذا التوقيع عن أي توقيع آخر يستخدم نفس wp_salt( 'auth' ).

في النهاية يحمل الرمز رابطًا بهذا الشكل: https://example.com/?checkin=42-3f9c….

موبايلات المنظمين فقط تسجّل الحضور

الزائر غير المسجَّل دخوله لا يستطيع فعل شيء بالرابط، سوى أن يُحوَّل إلى صفحة تسجيل الدخول ثم يعود. وبعد الدخول نفحص الصلاحية (بالدالة current_user_can قبل تنفيذ أي إجراء حساس مثل تعديل الحجوزات أو تسجيل الحضور.">Capability):

if ( ! is_user_logged_in() ) {
    wp_safe_redirect( wp_login_url( $checkin_url ) );
    exit;
}
if ( ! current_user_can( 'edit_others_posts' ) ) {
    // "This account cannot check people in."
}

كل متطوع يسجّل دخوله مرة واحدة على موبايله قبل فتح الأبواب، وبعدها: امسح، شوف النتيجة، اللي بعده.

ميزة جانبية مهمة: معاينات الروابط في واتساب وتيليجرام وبرامج فحص البريد تجلب الرابط دون كوكيز تسجيل الدخول. لذلك إرسال التذكرة في محادثة لن يسجّل حضور أحد بالخطأ.

نعم، طلب GET هنا يغيّر البيانات

القاعدة المعروفة أن طلب GET لا يغيّر شيئًا. لكن كاميرا الموبايل تفتح الرابط بطلب GET، وإضافة زر “تأكيد” في الصفحة تعني ضغطة إضافية لكل ضيف في أزحم لحظة.

الحل الذي اختاره الكاتب يعتمد على أمرين: فحص الصلاحية السابق، وأن تسجيل الحضور ختم في اتجاه واحد، أي طلب عديم الأثر عند التكرار (Idempotent Request). فتح الصفحة مرتين لا يفعل شيئًا لم تفعله المرة الأولى.

لو تسجيل الحضور عندك يفعل أكثر من ذلك، كخصم رصيد أو إصدار رقم سوار، فاستخدم طلب POST مع رمز التحقق المؤقت (Nonce) وتقبّل الضغطة الإضافية.

موبايلان وتذكرة واحدة

هذا هو الخلل الذي وقع فيه الكاتب فعلًا في النسخة الأولى. الكود كان يقرأ الحجز، يجد checked_in فارغًا، ثم يكتب الوقت. لو مسح متطوعان التذكرة نفسها في اللحظة ذاتها، كزوجين يتشاركان تذكرة، يقرأ الطلبان “لم يُسجَّل” ويظهر الأخضر على الموبايلين. هذه حالة تسابق (Race Condition) كلاسيكية.

الحل أن تجعل عملية الكتابة نفسها هي الفحص:

global $wpdb;
$n = $wpdb->query( $wpdb->prepare(
    'UPDATE %i SET checked_in = %s WHERE id = %d AND checked_in IS NULL',
    $table, gmdate( 'Y-m-d H:i:s' ), (int) $booking['id']
) );
$result = ( 1 === (int) $n ) ? 'ok' : 'already';

السيرفر.">قاعدة البيانات تسمح لتحديث واحد فقط بمطابقة الصف، فيحصل موبايل واحد على “ok”. الموبايل الآخر يعيد قراءة الصف ويعرض “تم تسجيله الساعة 7:42 م”، وهي بالضبط المعلومة التي يحتاجها المتطوع.

ملاحظة: العنصر %i الخاص بأسماء الجداول في prepare() يحتاج ووردبريس 6.2 أو أحدث. وللتأكد، اكتب اختبارًا يحمّل الصف نفسه مرتين ويسجّل الحضور بالنسختين: يجب أن يفشل على الكود القديم وينجح على الجديد.

شاشة تُقرأ من على بُعد ذراع

المداخل غالبًا مظلمة والناس مستعجلة، لذلك صفحة النتيجة كلمة واحدة على خلفية ملونة:

اللونالحالةماذا تعرض
أخضرتم تسجيل الحضورالاسم وعدد الأفراد ونوع التذكرة
———
كهرمانيمسجَّل من قبلوقت التسجيل الأول
أحمرمرفوضملغي، أو غير مدفوع، أو قائمة انتظار، أو رمز غير صحيح

وأرسل ترويسات noindex ومنع التخزين المؤقت (Cache) في صفحة التذكرة وصفحة تسجيل الحضور. لا يجب أن تظهر أي منهما في نتائج البحث أو في كاش مشترك، وانتبه لذلك خصوصًا لو تستخدم إضافة كاش مثل LiteSpeed Cache المنتشرة عند شركات الاستضافة في المنطقة.

ارسم الرمز داخل المتصفح

لا ترسل رابط التذكرة إلى خدمة خارجية تولّد صور QR. الرابط هنا بمثابة كلمة مرور، وإرساله يعني أنه سيبقى في سجلات طرف آخر. مكتبة صغيرة برخصة MIT مثل qrcode-generator ترسم الرمز في المتصفح بصيغة SVG:

var q = qrcode(0, 'M');           // auto size, medium error correction
q.addData(checkinUrl);
q.make();
document.getElementById('ticket-qr').innerHTML =
    q.createSvgTag({ cellSize: 6, margin: 2, scalable: true });

صيغة SVG تبقى حادة على أي حجم شاشة. ويمكنك التحقق من الناتج بفك رمز لقطة شاشة للتذكرة بمكتبة مثل jsQR ومقارنة الرابط الناتج بالأصلي.

في القالب كاملًا والنماذج والنوافذ المنبثقة.">Elementor + JetEngine: لو الحجوزات عندك مبنية كنوع محتوى في JetEngine مع نموذج JetFormBuilder، أضف حقلًا مخفيًا لتاريخ الحضور، واعرض الرمز في قالب التذكرة عبر ودجت HTML في Elementor يحتوي سكربت الرسم. لكن نفّذ منطق التسجيل نفسه بكود PHP كما في الأعلى، لأن شرط checked_in IS NULL هو ما يحميك من التسجيل المزدوج.

ما الذي لا يغطيه هذا الحل؟

  • يحتاج اتصالًا بالإنترنت عند الباب. لو القاعة في بدروم أو الشبكة ضعيفة، وهذا وارد في قاعات كثيرة، جهّز قائمة ورقية احتياطية بخانة لكل شخص.
  • تسجيل واحد لكل حجز. حجز لأربعة أشخاص يُمسح مرة واحدة، والشاشة تعرض “× 4” ليُدخل المتطوع المجموعة كلها.

الأسئلة الشائعة

س: هل أحتاج تطبيقًا لمسح تذاكر QR في ووردبريس؟
ج: لا. كاميرا الموبايل تفتح الرابط الموجود في الرمز، وصفحة على موقعك تتولى التحقق وتسجيل الحضور.

س: ماذا لو صوّر أحد الضيوف تذكرته ونشرها؟
ج: الرمز لا يفعل شيئًا إلا لمستخدم مسجَّل دخوله ولديه صلاحية مناسبة، ولا يحتوي رابط إلغاء الحجز.

س: كيف أمنع تسجيل التذكرة نفسها مرتين؟
ج: اجعل أمر التحديث مشروطًا بـ checked_in IS NULL، وتحقق من عدد الصفوف المتأثرة؛ واحد يعني نجاحًا، وصفر يعني أنها سُجلت من قبل.

س: هل يمكن استخدام موقع خارجي لتوليد صورة QR؟
ج: يُفضَّل تجنب ذلك، لأن رابط التذكرة سري وسيبقى في سجلات الخدمة الخارجية. ارسمه في المتصفح بمكتبة مثل qrcode-generator.

الخلاصة

بشوية PHP ومكتبة JavaScript صغيرة، تقدر تحوّل موقع ووردبريس لنظام تسجيل حضور كامل: رمز لا يُخمَّن، صلاحيات للمنظمين فقط، وحماية من التسجيل المزدوج. جرّبه في فعاليتك الجاية، وخلي الورقة المطبوعة في جيبك احتياطي بس.

مرجع المقال (بالإنجليزية): QR ticket check-in for a WordPress event, without a scanner app
المقال ده مش ترجمة حرفية: متكيّف للقارئ العربي ومضاف عليه سياق ووردبريس وElementor وJetEngine.

اترك تعليقاً