db = new Database($settings); // Ensure we return JSON responses header('Content-Type: application/json'); } /** * Handle booking submission */ public function booking() { if ($_SERVER['REQUEST_METHOD'] !== 'POST') { $this->response(['success' => false, 'message' => 'طريقة الطلب غير صحيحة'], 405); return; } // Retrieve and sanitize input $name = trim($_POST['name'] ?? ''); $phone = trim($_POST['phone'] ?? ''); $email = trim($_POST['email'] ?? ''); $appointment_type = $_POST['appointment_type'] ?? 'clinic'; $date = $_POST['date'] ?? ''; $time = $_POST['time'] ?? ''; $services_json = $_POST['services'] ?? '[]'; $address = trim($_POST['address'] ?? ''); $notes = trim($_POST['notes'] ?? ''); $payment_method = $_POST['payment_method'] ?? 'cash'; // Basic validation if ($name === '' || $phone === '' || $date === '' || $time === '') { $this->response(['success' => false, 'message' => 'يرجى ملء جميع الحقول المطلوبة'], 400); return; } // Validate date is not in the past $today = date('Y-m-d'); if ($date < $today) { $this->response(['success' => false, 'message' => 'لا يمكن اختيار تاريخ في الماضي'], 400); return; } // Validate time format (optional) if (!preg_match('/^([0-1][0-9]|2[0-3]):([0-5][0-9])$/', $time)) { $this->response(['success' => false, 'message' => 'تنسيق الوقت غير صالح'], 400); return; } // Generate booking number: OC-YYYY-0001 $year = date('Y'); // Get the last booking number for this year to increment $last = $this->db->first("SELECT booking_number FROM bookings WHERE booking_number LIKE ? ORDER BY id DESC LIMIT 1", ["OC-{$year}-%"]); $seq = 1; if ($last && preg_match('/-(\d+)$/', $last['booking_number'], $matches)) { $seq = intval($matches[1]) + 1; } $booking_number = sprintf("OC-%04d-%04d", $year, $seq); // Insert booking $data = [ 'booking_number' => $booking_number, 'name' => $name, 'phone' => $phone, 'email' => $email, 'appointment_type' => $appointment_type, 'date' => $date, 'time' => $time, 'services' => $services_json, 'address' => $address, 'notes' => $notes, 'payment_method' => $payment_method, 'status' => 'pending' ]; $user_id = null; // If user is logged in, attach user_id $session = new Session(new Settings()); if ($session->has('user_id')) { $user_id = $session->get('user_id'); $data['user_id'] = $user_id; } $insertId = $this->db->insert('bookings', $data); if ($insertId) { // Optionally send email/SMS notification (not implemented) $this->response([ 'success' => true, 'message' => 'تم حجز الموعد بنجاح', 'booking_number' => $booking_number ]); } else { $this->response(['success' => false, 'message' => 'فشل في حفظ الحجز. يرجى المحاولة لاحقًا.'], 500); } } /** * Handle contact form submission */ public function contact() { if ($_SERVER['REQUEST_METHOD'] !== 'POST') { $this->response(['success' => false, 'message' => 'طريقة الطلب غير صحيحة'], 405); return; } $name = trim($_POST['name'] ?? ''); $email = trim($_POST['email'] ?? ''); $phone = trim($_POST['phone'] ?? ''); $subject = $_POST['subject'] ?? ''; $message = trim($_POST['message'] ?? ''); if ($name === '' || $email === '' || $message === '') { $this->response(['success' => false, 'message' => 'يرجى ملء جميع الحقول المطلوبة'], 400); return; } // In a real application, you would store this in a contact_messages table or send an email. // For now, we just log it or return success. // We'll insert into a simple contact_messages table if exists, but we can just respond success. // For demonstration, we'll just return success. // Optionally, you could save to a database table. // Let's create a simple log file or just respond. // We'll attempt to insert into a contact_messages table if it exists; if not, we ignore. try { $this->db->insert('contact_messages', [ 'name' => $name, 'email' => $email, 'phone' => $phone, 'subject' => $subject, 'message' => $message, 'created_at' => date('Y-m-d H:i:s') ]); } catch (\Throwable $e) { // Table may not exist; we still consider the contact successful. } $this->response([ 'success' => true, 'message' => 'تم إرسال رسالتنا بنجاح. سيتواصل معك فريقنا قريبًا.' ]); } /** * Get available time slots for a given date */ public function slots() { if ($_SERVER['REQUEST_METHOD'] !== 'GET') { $this->response(['success' => false, 'message' => 'طريقة الطلب غير صحيحة'], 405); return; } $date = $_GET['date'] ?? ''; if ($date === '') { $this->response(['success' => false, 'message' => 'التاريخ مطلوب'], 400); return; } // Determine day of week (0=Sunday, ..., 6=Saturday) $timestamp = strtotime($date); if ($timestamp === false) { $this->response(['success' => false, 'message' => 'تنسيق التاريخ غير صالح'], 400); return; } $dayOfWeek = (int)date('w', $timestamp); // 0 Sunday, 6 Saturday // Check date overrides first $override = $this->db->first("SELECT * FROM date_overrides WHERE date = ?", [$date]); $isClosed = false; $startTime = null; $endTime = null; if ($override) { if ($override['override_type'] === 'closed') { $isClosed = true; } elseif ($override['override_type'] === 'open') { $startTime = $override['start_time']; $endTime = $override['end_time']; } elseif ($override['override_type'] === 'period_closed') { // period closed: treat as closed for the given period; we need to compute available slots outside that period // For simplicity, we'll treat as closed all day $isClosed = true; } } if (!$isClosed && !$startTime) { // Get regular work hours for this day $workHour = $this->db->first("SELECT * FROM work_hours WHERE day_of_week = ? AND is_active = 1", [$dayOfWeek]); if (!$workHour) { $this->response(['success' => true, 'slots' => []]); // no work hours return; } $startTime = $workHour['start_time']; $endTime = $workHour['end_time']; $breakStart = $workHour['break_start']; $breakEnd = $workHour['break_end']; } if ($isClosed) { $this->response(['success' => true, 'slots' => []]); return; } // Generate slots in 30-minute intervals $slots = []; $current = strtotime($startTime); $end = strtotime($endTime); $interval = 30 * 60; // 30 minutes in seconds while ($current < $end) { $slotStart = date('H:i:s', $current); $slotEnd = date('H:i:s', $current + $interval); // Check if slot overlaps with break $overlapsBreak = false; if ($breakStart && $breakEnd) { $breakStartTs = strtotime($breakStart); $breakEndTs = strtotime($breakEnd); if ($current >= $breakStartTs && $current < $breakEndTs) { $overlapsBreak = true; } // Also if slot crosses break boundary? We'll assume slot duration <= break duration; ignore for simplicity. } if (!$overlapsBreak) { $slots[] = $slotStart; // store start time } $current += $interval; } // Also need to exclude already booked slots for this date $booked = $this->db->select("SELECT time FROM bookings WHERE date = ? AND status IN ('pending','confirmed','in_progress')", [$date]); $bookedTimes = array_column($booked ?? [], 'time'); $slots = array_diff($slots, $bookedTimes); $this->response(['success' => true, 'slots' => array_values($slots)]); } /** * Helper to send JSON response * * @param array $data * @param int $status */ protected function response(array $data, int $status = 200) { http_response_code($status); echo json_encode($data); exit; }}