status != 'active') { return $this->sendError([ 'message' => __('Sorry, the host is not accepting any new bookings at the moment.', 'fluent-booking') ]); } $calendar = $slot->calendar; $startDate = $request->get('start_date', gmdate('Y-m-d H:i:s')); $timeZone = $request->get('timezone', 'UTC'); if (!$timeZone) { $timeZone = 'UTC'; } $timeSlotService = TimeSlotServiceHandler::initService($calendar, $slot); if (is_wp_error($timeSlotService)) { return TimeSlotServiceHandler::sendError($timeSlotService, $slot, $timeZone); } $availableSpots = $timeSlotService->getAvailableSpots($startDate, $timeZone); if(is_wp_error($availableSpots)) { return [ 'available_slots' => [], 'timezone' => $timeZone, 'invalid_dates' => true, 'max_lookup_date' => $slot->getMaxLookUpDate(), ]; } return [ 'available_slots' => array_filter($availableSpots), 'timezone' => $timeZone, 'max_lookup_date' => $slot->getMaxLookUpDate(), ]; } public function createBooking(Request $request, $eventId) { $calendarEvent = CalendarSlot::findOrfail($eventId); if ($calendarEvent->status != 'active') { return $this->sendError([ 'message' => __('Sorry, the host is not accepting any new bookings at the moment.', 'fluent-booking') ]); } $postedData = $request->all(); $rules = [ 'name' => 'required', 'email' => 'required|email', 'timezone' => 'required', 'event_time' => 'required' ]; $messages = [ 'name.required' => __('Please enter attendee\'s name', 'fluent-booking'), 'email.required' => __('Please enter attendee\'s email address', 'fluent-booking'), 'email.email' => __('Please provide a valid email address', 'fluent-booking'), 'timezone.required' => __('Please select the timezone', 'fluent-booking'), 'event_time.required' => __('Please select a date and time', 'fluent-booking') ]; $locationType = Arr::get($postedData, 'location_type'); if ($calendarEvent->isPhoneRequired()) { $rules['location_description'] = ['required', $this->validPhoneNumberRule()]; $messages['location_description.required'] = __('Please provide attendee\'s phone number', 'fluent-booking'); } else if ($calendarEvent->isAddressRequired()) { $rules['location_description'] = 'required'; $messages['location_description.required'] = __('Please provide attendee\'s address', 'fluent-booking'); } else if ($locationType === 'phone_guest') { $rules['location_description'] = [$this->validPhoneNumberRule()]; } $additionalGuests = Arr::get($postedData, 'guests', []); if (!empty($additionalGuests)) { if ($calendarEvent->isMultiGuestEvent()) { $additionalGuests = $this->sanitize_mapped_data($additionalGuests); $additionalGuests = array_values(array_filter($additionalGuests, function ($guest) { return Arr::get($guest, 'name') && Arr::get($guest, 'email'); })); } else { $additionalGuests = array_filter(array_map('sanitize_email', $additionalGuests)); } } $postedData['guests'] = $additionalGuests; $requiredFields = array_filter($calendarEvent->getMeta('booking_fields', []), function ($field) { return Arr::isTrue($field, 'required') && Arr::isTrue($field, 'enabled') && (Arr::get($field, 'name') == 'message' || Arr::get($field, 'name') == 'guests'); }); foreach ($requiredFields as $field) { if (empty($rules[$field['name']])) { $rules[$field['name']] = 'required'; $messages[$field['name'] . '.required'] = __('This field is required', 'fluent-booking'); } } $validationConfig = apply_filters('fluent_booking/schedule_validation_rules_data', [ 'rules' => $rules, 'messages' => $messages ], $postedData, $calendarEvent); $app = App::getInstance(); $validator = $app->validator->make($postedData, $validationConfig['rules'], $validationConfig['messages']); if ($validator->validate()->fails()) { wp_send_json([ 'message' => __('Please fill up the required data', 'fluent-booking'), 'errors' => $validator->errors() ], 422); return; } $customFieldsData = BookingFieldService::getCustomFieldsData(Arr::get($postedData, 'custom_fields', []), $calendarEvent); $customFieldsData = apply_filters('fluent_booking/schedule_custom_field_data', $customFieldsData, $calendarEvent); if (is_wp_error($customFieldsData)) { wp_send_json([ 'message' => $customFieldsData->get_error_message(), 'errors' => $customFieldsData->get_error_data() ], 422); return; } $duration = $calendarEvent->getDuration(Arr::get($postedData, 'duration', null)); $timezone = sanitize_text_field($postedData['timezone']); $startDateTime = DateTimeHelper::convertToUtc(sanitize_text_field($postedData['event_time']), $timezone); $endDateTime = gmdate('Y-m-d H:i:s', strtotime($startDateTime) + ($duration * 60)); $bookingData = apply_filters('fluent_booking/initialize_booking_data', [ 'person_time_zone' => $timezone, 'start_time' => $startDateTime, 'name' => sanitize_text_field($postedData['name']), 'email' => sanitize_email($postedData['email']), 'message' => sanitize_textarea_field(wp_unslash(Arr::get($postedData, 'message', ''))), 'phone' => sanitize_text_field(Arr::get($postedData, 'phone_number', '')), 'address' => sanitize_textarea_field(Arr::get($postedData, 'address', '')), 'ip_address' => Helper::getIp(), 'status' => sanitize_text_field(Arr::get($postedData, 'status', 'scheduled')), 'source' => Arr::get($postedData, 'source') == 'admin' ? 'admin' : 'web', 'event_type' => $calendarEvent->event_type, 'slot_minutes' => $duration ], $postedData, $calendarEvent); $eventLocations = []; $locationSettings = $calendarEvent->location_settings; foreach ($locationSettings as $index => $location) { $eventLocations[$location['type']] = $location; } $locationDetails['type'] = $locationType; if ($locationType == 'phone_organizer') { $locationDetails['description'] = $eventLocations[$locationType]['host_phone_number']; } else if ($locationType == 'phone_guest') { $bookingData['phone'] = sanitize_text_field(Arr::get($postedData, 'location_description', '')); } else if ($locationType == 'in_person_guest') { $locationDetails['description'] = sanitize_textarea_field(Arr::get($postedData, 'location_description', '')); } else if (in_array($locationType, ['custom', 'in_person_organizer'])) { $locationDetails['description'] = $eventLocations[$locationType]['description']; } else if (in_array($locationType, ['google_meet', 'online_meeting', 'zoom_meeting', 'ms_teams'])) { $locationDetails['description'] = Arr::get($eventLocations[$locationType], 'meeting_link', ''); $locationDetails['online_platform_link'] = $locationDetails['description']; } $bookingData['location_details'] = $locationDetails; if ($sourceUrl = Arr::get($postedData, 'source_url', '')) { $bookingData['source_url'] = sanitize_url($sourceUrl); } if ($hostUserId = Arr::get($postedData, 'host_user_id', null)) { $hostUserId = (int) $hostUserId; if (!in_array($hostUserId, array_map('intval', $calendarEvent->getHostIds()), true)) { return $this->sendError([ 'message' => __('The selected host is not a host of this event', 'fluent-booking') ], 422); } $bookingData['host_user_id'] = $hostUserId; } $hostIds = null; if ($calendarEvent->isRoundRobin() && !$hostUserId) { $hostIds = $calendarEvent->getHostIdsSortedByBookings($startDateTime); $bookingData['host_user_id'] = $hostIds[0]; } $availableSpot = false; if (!Arr::isTrue($postedData, 'ignore_availability')) { $timeSlotService = TimeSlotServiceHandler::initService($calendarEvent->calendar, $calendarEvent); if (is_wp_error($timeSlotService)) { return TimeSlotServiceHandler::sendError($timeSlotService, $calendarEvent, $timezone); } $availableSpot = $timeSlotService->isSpotAvailable($startDateTime, $endDateTime, $duration, $hostUserId); if (!$availableSpot) { wp_send_json([ 'message' => __('This selected time slot is not available. Maybe someone booked the spot just a few seconds ago.', 'fluent-booking') ], 422); } if ($calendarEvent->isRoundRobin() && !$hostUserId) { $bookingData['host_user_id'] = $timeSlotService->hostUserId; } } if ($additionalGuests) { $guestField = BookingFieldService::getBookingFieldByName($calendarEvent, 'guests'); $guestLimit = Arr::get($guestField, 'limit', 10); if ($calendarEvent->isMultiGuestEvent() && $availableSpot) { $remaining = Arr::get($availableSpot, 'remaining', $calendarEvent->getMaxBookingPerSlot()); $guestLimit = min($remaining, $guestLimit) - 1; } $bookingData['additional_guests'] = array_slice($additionalGuests, 0, $guestLimit); } do_action('fluent_booking/before_creating_schedule', $bookingData, $postedData, $calendarEvent); try { $booking = BookingService::createBooking($bookingData, $calendarEvent, $customFieldsData); do_action('fluent_booking/after_creating_schedule', $booking, $postedData, $calendarEvent); } catch (\Exception $e) { wp_send_json([ 'message' => $e->getMessage() ], $e->getCode()); } return [ 'booking' => $booking, 'message' => __('Booking has been created', 'fluent-booking'), ]; } /** * @return \Closure */ private function validPhoneNumberRule() { return function ($attribute, $value) { if (!empty($value) && !Helper::isValidPhoneNumber($value)) { return __('Please provide a valid phone number', 'fluent-booking'); } }; } public function getEvent(Request $request, $eventId) { $calendarEvent = CalendarSlot::find($eventId); if (!$calendarEvent || $calendarEvent->status != 'active') { wp_send_json([ 'message' => __('Sorry, the host is not accepting any new bookings at the moment.', 'fluent-booking') ], 422); } $calendar = $calendarEvent->calendar; if (!$calendar) { return $this->sendError([ 'message' => __('Calendar not found', 'fluent-booking') ]); } $calendarEventVars = (new FrontEndHandler())->getCalendarEventVars($calendar, $calendarEvent); $startDate = $request->get('start_date'); if (!$startDate) { $startDate = gmdate('Y-m-d H:i:s'); } $timeZone = $request->get('timezone'); if (!$timeZone) { $timeZone = wp_timezone_string(); } if (!in_array($timeZone, \DateTimeZone::listIdentifiers())) { $timeZone = $calendar->author_timezone; } $duration = $calendarEvent->getDuration($request->get('duration')); $hostId = $request->get('host_id', null); if ($hostId) { $hostId = (int) $hostId; if (!in_array($hostId, array_map('intval', $calendarEvent->getHostIds()), true)) { wp_send_json([ 'message' => __('The selected host is not a host of this event', 'fluent-booking') ], 422); } } $timeSlotService = TimeSlotServiceHandler::initService($calendar, $calendarEvent); if (is_wp_error($timeSlotService)) { return TimeSlotServiceHandler::sendError($timeSlotService, $calendarEvent, $timeZone); } $availableSpots = $timeSlotService->getAvailableSpots($startDate, $timeZone, $duration, $hostId); if (is_wp_error($availableSpots)) { wp_send_json([ 'available_slots' => [], 'calendar_event' => $calendarEventVars, 'timezone' => $timeZone, 'invalid_dates' => true ], 200); } $availableSpots = apply_filters('fluent_booking/available_slots_for_view', array_filter($availableSpots), $calendarEvent, $calendar, $timeZone, $duration); return [ 'calendar_event' => $calendarEventVars, 'available_slots' => $availableSpots ]; } public function getBookings(Request $request) { $userData = get_userdata(get_current_user_id()); $userEmail = $userData ? $userData->user_email : null; if (!$userEmail) { return $this->sendError([ 'message' => __('Please login to view your bookings', 'fluent-booking') ]); } $perPage = intval($request->get('per_page', 10)); $bookingPeriod = sanitize_text_field($request->get('period', 'all')); $bookingQuery = Booking::query()->with('calendar_event') ->where('email', $userEmail) ->applyComputedStatus($bookingPeriod) ->applyBookingOrderByStatus($bookingPeriod); $calendarIds = $request->get('calendar_ids', []); if (!in_array('all', $calendarIds)) { $calendarIds = array_map('intval', $calendarIds); $bookingQuery->whereIn('calendar_id', $calendarIds); } do_action_ref_array('fluent_booking/bookings_query', [&$bookingQuery]); $totalBookings = $bookingQuery->count(); $bookings = $bookingQuery->limit($perPage)->get(); $formattedBookings = []; foreach ($bookings as $booking) { $formattedBookings[] = [ 'id' => $booking->id, 'person_time_zone' => $booking->person_time_zone, 'status' => ucfirst($booking->status), 'payment_status' => $booking->payment_status, 'booking_title' => $booking->getBookingTitle(true), 'author_name' => $booking->getHostDetails(false)['name'], 'booking_date' => DateTimeHelper::formatToLocale($booking->getAttendeeStartTime(), 'date'), 'booking_time' => DateTimeHelper::formatToLocale($booking->getAttendeeStartTime(), 'time') . ' - ' . DateTimeHelper::formatToLocale($booking->getAttendeeEndTime(), 'time'), ]; } return [ 'bookings' => $formattedBookings, 'total' => $totalBookings ]; } private static function sanitize_mapped_data($settings) { $sanitizerMap = [ 'name' => 'sanitize_text_field', 'email' => 'sanitize_email', ]; return Helper::fcal_backend_sanitizer($settings, $sanitizerMap); } }