افزونه رزرو اختصاصی
a custom wordpress reservation plugin
by تیم وبسایت مدیکال استراتژیست · github.com/alirezakmaxim/custom-reservation · website
Install
No release zip yet. The repository archive installs, but the folder name will carry the branch suffix and updates will not flow:
wp plugin install https://github.com/alirezakmaxim/custom-reservation/archive/refs/heads/main.zipاین سند «منبع حقیقت» (Source of Truth) برای توسعه، نگهداری و انتقال دانش افزونه است.
فهرست مطالب
- [مقدمه و اهداف]
- [نمای کلی معماری (Architecture Overview)]
- [ساختار فایلها و پوشهبندی (File Structure)]
- [طراحی پایگاه داده (Database Schema)]
- [قراردادهای API (API Contracts)]
- [الگوریتمهای کلیدی (Core Algorithms)]
- [جریان داده (Data Flow)]
- [ملاحظات امنیتی (Security Measures)]
1. مقدمه و اهداف
هدف این سند ایجاد یک مرجع واحد، دقیق و قابل اتکا برای فهم و توسعه افزونه رزرو اختصاصی در وردپرس است؛ به طوری که هر عضو تیم بتواند بدون حدس و گمان، معماری، قراردادها و جزئیات پیادهسازی را دنبال کند.
اهداف کلیدی:
- حذف ابهام در رفتار سیستم و قراردادها
- تسهیل توسعه و نگهداری (Maintainability)
- کاهش ریسک خطاهای همزمانی (Race Conditions) در رزرو
- تضمین امنیت (CSRF/XSS/SQLi) در سطح API و دیتابیس
2. نمای کلی معماری (Architecture Overview)
سیستم بر پایه الگوی Event-Driven MVC در بستر وردپرس طراحی شده تا تفکیک وظایف (Separation of Concerns) حداکثر شود و توسعه، نگهداری و تست سادهتر گردد.
Stack: HTML5, CSS3, JavaScript (AJAX, ES6), PHP, SQL
2.1 الگوی طراحی: Event-Driven MVC
- MVC برای جداسازی داده، منطق و نما استفاده میشود.
- ارتباط بین اجزا عمدتاً از طریق رویدادها/هوکهای وردپرس انجام میشود تا وابستگی مستقیم کاهش یابد.
- تمامی درخواستهای کاربر (فرانت و ادمین) از طریق کنترلرها وارد سیستم میشوند.
2.2 نقش هر لایه
Model (مدل)
- مکان:
/models/ - مسئولیت: تعامل مستقیم با پایگاه داده از طریق
$wpdbو انجام عملیات CRUD. - نکته: مدلها منطق UI یا رندر ندارند و تا حد امکان از منطق تجاری سنگین دور نگه داشته میشوند.
- کلاسهای کلیدی:
CR_Reservation،CR_Schedule_Rule،CR_Date_Manager
View (نما)
- مکان:
/views/ - مسئولیت: رندر HTML و تولید خروجی قابل نمایش.
- ادمین: رندر سمت سرور (SSR) برای صفحات مدیریت.
- فرانت: قالبهای پایه که توسط JS و شورتکد
[reservation_booking]پر میشوند.
Controller (کنترلر)
- مکان:
/controllers/ - مسئولیت: دریافت درخواست، اعتبارسنجی ورودی، فراخوانی سرویس/مدلها و برگرداندن پاسخ مناسب (HTML یا JSON).
- کلاسهای کلیدی:
CR_Booking_Controller،CR_Admin_Controller
Event Bus (گذرگاه رویداد)
- مکان:
includes/class-loader.php - مسئولیت: مدیریت متمرکز
add_actionوadd_filterو ثبت همه هوکها در یک نقطه. - مزیت: کاهش coupling و افزایش قابلیت توسعه و تست.
3. ساختار فایلها و پوشهبندی (File Structure)
ساختار پروژه مطابق MVC و برای تفکیک وظایف بهینه سازماندهی شده است:
/custom-reservation/
├── custom-reservation.php # نقطه شروع افزونه (تعریف ثابتها، لودر خودکار، هوکهای فعالسازی)
├── assets/ # فایلهای استاتیک (CSS, JS, Images)
│ ├── css/
│ └── js/
├── includes/ # هسته و زیرساخت افزونه
│ ├── class-loader.php # ثبت و مدیریت متمرکز تمام هوکهای وردپرس
│ └── class-activator.php # نصب، ارتقاء و ساخت جداول دیتابیس (dbDelta)
├── libs/ # کتابخانههای کمکی
│ └── class-jalali-converter.php # تبدیل تاریخ میلادی↔شمسی
├── models/ # لایه داده (تعامل با دیتابیس)
│ ├── class-reservation.php # جدول رزروها (wp_cr_reservations)
│ ├── class-schedule-rule.php # قوانین هفتگی (wp_cr_schedule_rules)
│ └── class-date-manager.php # استثناهای تقویم (wp_cr_date_ranges)
├── services/ # منطق تجاری
│ ├── class-slot-generator.php # تولید اسلاتهای زمانی خالی
│ └── class-email-service.php # ارسال ایمیلهای تراکنشی
├── controllers/ # مدیریت درخواستها
│ ├── class-booking-controller.php # فرآیند رزرو در فرانتاند
│ └── class-admin-controller.php # بخش ادمین (منوها، تنظیمات)
└── views/ # قالبهای HTML
├── booking-wizard.php # فرم رزرو چندمرحلهای
└── admin/
├── dashboard.php # لیست رزروها
├── calendar.php # مدیریت تقویم/تعطیلات
└── settings.php # تنظیمات افزونه
4. طراحی پایگاه داده (Database Schema)
افزونه با پیشوند cr_ سه جدول اختصاصی ایجاد میکند. پیشوند wp_ بسته به تنظیمات وردپرس ممکن است تغییر کند.
4.1 جدول رزروها (wp_cr_reservations)
ذخیره تمامی نوبتهای ثبتشده توسط کاربران.
| ستون | نوع داده | توضیحات |
|---|---|---|
| id | BIGINT UNSIGNED | شناسه یکتا (Primary Key, Auto Increment) |
| reservation_type | VARCHAR(20) | نوع نوبت (online یا in_person) |
| jalali_date | VARCHAR(10) | تاریخ شمسی (مثال: 1403/11/20) |
| gregorian_date | DATE | تاریخ میلادی (برای کوئریها و مرتبسازی) |
| time_slot | VARCHAR(5) | ساعت نوبت (مثال: 14:30) |
| first_name | VARCHAR(100) | نام |
| last_name | VARCHAR(100) | نام خانوادگی |
| phone | VARCHAR(15) | شماره تماس (فرمت استاندارد: 09xxxxxxxxx) |
| description | TEXT | توضیحات اختیاری |
| confirmation_code | CHAR(3) | کد پیگیری کوتاه (تولید تصادفی) |
| status | VARCHAR(20) | وضعیت (reserved, deleted) |
| created_at | DATETIME | زمان ایجاد رکورد |
ایندکسها و محدودیتها:
UNIQUE KEY (reservation_type, gregorian_date, time_slot)برای جلوگیری از رزرو تکراری روی یک اسلات مشخص.KEY (status)وKEY (phone)برای بهبود سرعت جستجو در پیشخوان.
4.2 جدول قوانین هفتگی (wp_cr_schedule_rules)
| ستون | نوع داده | توضیحات |
|---|---|---|
| id | INT UNSIGNED | شناسه یکتا (Primary Key) |
| reservation_type | VARCHAR(20) | نوع نوبت (online یا in_person) |
| weekday | TINYINT | روز هفته (0=شنبه تا 6=جمعه) |
| start_time | VARCHAR(5) | ساعت شروع (مثال: 08:00) |
| end_time | VARCHAR(5) | ساعت پایان (مثال: 12:00) |
| interval_minutes | TINYINT | طول هر نوبت به دقیقه (مثال: 15) |
| is_active | TINYINT(1) | فعال/غیرفعال (1=فعال) |
4.3 جدول استثناهای تقویم (wp_cr_date_ranges)
| ستون | نوع داده | توضیحات |
|---|---|---|
| id | BIGINT UNSIGNED | شناسه یکتا (Primary Key) |
| start_date | DATE | تاریخ شروع بازه استثنا |
| end_date | DATE | تاریخ پایان بازه استثنا |
| is_working_day | TINYINT(1) | آیا روز کاری است؟ (1=بله، 0=تعطیل) |
| start_time | VARCHAR(5) | ساعت شروع (اگر is_working_day = 1) |
| end_time | VARCHAR(5) | ساعت پایان (اگر is_working_day = 1) |
5. قراردادهای API (API Contracts)
تمامی ارتباطات سمت کلاینت با سرور از طریق درخواستهای POST به admin-ajax.php و با پاسخهای JSON انجام میشود. همه درخواستها باید شامل nonce معتبر باشند.
قواعد عمومی
- URL:
wp-admin/admin-ajax.php - Method: POST
- Nonce: پیشنهاد:
cr_nonce(یا هر نامی که در فرانت تولید میکنید) - پاسخ استاندارد:
- موفق:
{ "success": true, "data": {...} } - خطا:
{ "success": false, "data": { "code": "...", "message": "..." } }
- موفق:
5.1 دریافت تاریخهای آزاد
Action: cr_get_available_dates
Method: POST
پارامترها: action, reservation_type (online|in_person), nonce
نمونه پاسخ موفق:
{
"success": true,
"data": {
"dates": [
{
"gregorian_date": "2024-02-10",
"jalali_date": "1402/11/21",
"weekday": 0,
"available_slots": 12,
"display": "شنبه ۲۱ بهمن"
}
]
}
}
5.2 دریافت ساعتهای خالی
Action: cr_get_time_slots
Method: POST
پارامترها: action, reservation_type, gregorian_date (YYYY-MM-DD), nonce
پارامترهای ورودی
| نام | نوع | اجباری | توضیح |
|---|---|---|---|
| action | string | ✅ | مقدار ثابت cr_get_time_slots |
| reservation_type | string | ✅ | online یا in_person |
| gregorian_date | string | ✅ | فرمت YYYY-MM-DD |
| nonce | string | ✅ | nonce معتبر |
خطاهای رایج
INVALID_NONCE: nonce نامعتبرINVALID_TYPE: نوع رزرو نامعتبرINVALID_DATE: تاریخ نامعتبر
نمونه پاسخ موفق
{
"success": true,
"data": {
"gregorian_date": "2024-02-10",
"reservation_type": "online",
"slots": [
{ "time": "08:00", "available": true, "display": "08:00" },
{ "time": "08:15", "available": false, "display": "08:15" }
]
}
}
نمونه پاسخ خطا
{
"success": false,
"data": {
"code": "INVALID_DATE",
"message": "فرمت تاریخ نامعتبر است."
}
}
5.3 ثبت نهایی رزرو
Action: cr_submit_reservation
Method: POST
پارامترهای ورودی
| نام | نوع | اجباری | توضیح |
|---|---|---|---|
| action | string | ✅ | مقدار ثابت cr_submit_reservation |
| first_name | string | ✅ | نام |
| last_name | string | ✅ | نام خانوادگی |
| phone | string | ✅ | ترجیحاً 09xxxxxxxxx |
| time_slot | string | ✅ | HH:MM |
| gregorian_date | string | ✅ | YYYY-MM-DD |
| jalali_date | string | ✅ | YYYY/MM/DD |
| reservation_type | string | ✅ | online یا in_person |
| description | string | ❌ | توضیحات اختیاری |
| nonce | string | ✅ | nonce معتبر |
قوانین اعتبارسنجی
phone: الگو مثل^09\d{9}$time_slot: الگو مثل^\d{2}:\d{2}$و باید جزء اسلاتهای همان روز باشد- جلوگیری از رزرو تکراری:
- چک نرمافزاری قبل از insert
- و اتکا به
UNIQUE KEYدیتابیس
نمونه پاسخ موفق
{
"success": true,
"data": {
"message": "رزرو با موفقیت انجام شد",
"confirmation_code": "854",
"reservation": {
"reservation_type": "online",
"gregorian_date": "2024-02-10",
"jalali_date": "1402/11/21",
"time_slot": "08:00",
"first_name": "علی",
"last_name": "رضایی",
"phone": "09123456789",
"status": "reserved"
}
}
}
نمونه پاسخ خطا (اسلات قبلاً رزرو شده)
{
"success": false,
"data": {
"code": "SLOT_ALREADY_RESERVED",
"message": "این زمان قبلاً رزرو شده است. لطفاً زمان دیگری انتخاب کنید."
}
}
6. الگوریتمهای کلیدی (Core Algorithms)
6.1 تولید اسلاتهای زمانی (CR_Slot_Generator::generate_slots)
این الگوریتم قلب سیستم است و با ترکیب «قوانین هفتگی» و «استثناهای تقویم»، لیست نهایی اسلاتهای قابل رزرو را تولید میکند.
مراحل اجرا:
- بررسی استثناهای تقویم با
CR_Date_Manager:- اگر
is_working_day = 0→ روز تعطیل → خروجی آرایه خالی - اگر
is_working_day = 1وstart_time/end_timeمشخص باشد → ساعات استثنا جایگزین قوانین هفتگی میشوند
- اگر
- اگر استثنا نبود: قانون هفتگی همان
weekdayازCR_Schedule_Ruleخوانده میشود. - تولید اسلات خام: حلقه از
start_timeتاend_timeبا گامinterval_minutes. - خواندن ساعات رزرو شده از
CR_Reservation::get_reserved_slots. - علامتگذاری
availableو بازگرداندن نتیجه.
6.2 محاسبه روزهای آزاد (برای تقویم)
برای اکشن cr_get_available_dates:
- بازهای از روزهای آینده (مثلاً N روز) پیمایش میشود
- برای هر روز اسلاتها تولید میشوند
- اگر تعداد اسلات آزاد > 0 باشد آن روز در خروجی قرار میگیرد
نکته عملکردی: برای N بزرگ، استفاده از Transient برای کش کوتاهمدت پیشنهاد میشود.
6.3 جلوگیری از تداخل رزرو (Race Condition)
دو لایه دفاع:
- لایه نرمافزاری:
is_slot_reserved()درست قبل از insert - لایه دیتابیس:
UNIQUE (reservation_type, gregorian_date, time_slot)- اگر خطای duplicate رخ داد → همان پیام «اسلات قبلاً رزرو شده»
7. جریان داده (Data Flow): فرآیند ثبت رزرو
مسیر کامل رزرو از دید کاربر و سیستم:
- کاربر صفحه حاوی شورتکد
[reservation_booking]را باز میکند. CR_Booking_Controllerقالبbooking-wizard.phpرا رندر میکند.- فرانتاند درخواست
cr_get_available_datesارسال میکند. - کنترلر ورودیها را اعتبارسنجی کرده و
CR_Slot_Generatorرا فراخوانی میکند. - پاسخ JSON میآید و تقویم در UI نمایش داده میشود.
- کاربر تاریخ را انتخاب میکند و درخواست
cr_get_time_slotsارسال میشود. - کنترلر اسلاتها را تولید/علامتگذاری کرده و JSON برمیگرداند.
- کاربر یک ساعت خالی را انتخاب میکند و فرم را تکمیل میکند و
cr_submit_reservationارسال میشود. - کنترلر nonce را بررسی و ورودیها را sanitize میکند.
- قبل از ثبت نهایی برای جلوگیری از Race Condition دوباره چک میشود.
- اگر اسلات خالی باشد رکورد جدید ساخته میشود و
confirmation_codeتولید میگردد. CR_Email_Serviceایمیل تایید را ارسال میکند.- پاسخ موفقیت همراه کد پیگیری به کاربر بازگردانده میشود.
8. ملاحظات امنیتی (Security Measures)
8.1 Nonce Verification (CSRF Protection)
- همه درخواستهای AJAX باید nonce معتبر داشته باشند.
- در کنترلر با
wp_verify_nonce()بررسی شوند. - در صورت خطا:
- کد:
INVALID_NONCE
- کد:
8.2 Input Sanitization & Validation
- هیچ دادهای بدون پاکسازی وارد دیتابیس نمیشود:
sanitize_text_field()برای فیلدهای کوتاهsanitize_textarea_field()برای توضیحات
- اعتبارسنجیهای سختگیرانه:
phoneبا Regex- تاریخ و ساعت با Regex + اعتبارسنجی منطقی
8.3 SQL Injection Prevention
- تمام کوئریها با
$wpdb->prepare()نوشته شوند. - از الحاق مستقیم رشتهها در SQL اجتناب شود.
8.4 Access Control (Admin)
- برای صفحات/اکشنهای ادمین بررسی دسترسی با
current_user_can()الزامی است. - پیشنهاد: محدودسازی به
manage_options
8.5 XSS Prevention
- در View ها:
- متن:
esc_html() - attribute:
esc_attr() - URL:
esc_url()
- متن:
8.6 Rate Limiting
برای جلوگیری از سوءاستفاده:
- محدودسازی تعداد
cr_submit_reservationبر اساس IP/شماره در بازه زمانی (Transient) - کد خطا:
RATE_LIMITED
8.7 Privacy & Logging
- از ذخیره شماره تلفن کامل در لاگها خودداری شود (ماسک/هش).
- در خروجی API از ارسال دادههای غیرضروری خودداری شود (Minimal Exposure)