PluginProbe
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution / 1.6.0
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution v1.6.0
2.4.0 2.3.0 2.2.5 2.2.0 2.1.2 2.1.1 trunk 1.10.0 1.10.01 1.10.02 1.5.0 1.5.01 1.5.02 1.5.1 1.5.10 1.5.20 1.5.21 1.5.22 1.5.23 1.5.24 1.5.25 1.6.0 1.7.0 1.7.1 1.7.2 All 33 releases
fluent-booking / app / Http / Controllers / BookingController.php

BookingController.php in Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution 1.6.0, at app/Http/Controllers/BookingController.php

383 lines 15.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentBooking\App\Http\Controllers;
4
5 use FluentBooking\App\App;
6 use FluentBooking\App\Models\Booking;
7 use FluentBooking\App\Models\CalendarSlot;
8 use FluentBooking\App\Services\BookingService;
9 use FluentBooking\App\Services\DateTimeHelper;
10 use FluentBooking\App\Services\BookingFieldService;
11 use FluentBooking\App\Hooks\Handlers\FrontEndHandler;
12 use FluentBooking\App\Hooks\Handlers\TimeSlotServiceHandler;
13 use FluentBooking\Framework\Http\Request\Request;
14 use FluentBooking\App\Services\Helper;
15 use FluentBooking\Framework\Support\Arr;
16
17 class BookingController extends Controller
18 {
19 public function getSlots(Request $request, $slotId)
20 {
21 $slot = CalendarSlot::findOrfail($slotId);
22
23 if ($slot->status != 'active') {
24 return $this->sendError([
25 'message' => __('Sorry, the host is not accepting any new bookings at the moment.', 'fluent-booking')
26 ]);
27 }
28
29 $calendar = $slot->calendar;
30 $startDate = $request->get('start_date', gmdate('Y-m-d H:i:s'));
31 $timeZone = $request->get('timezone', 'UTC');
32
33 if (!$timeZone) {
34 $timeZone = 'UTC';
35 }
36
37 $timeSlotService = TimeSlotServiceHandler::initService($calendar, $slot);
38
39 if (is_wp_error($timeSlotService)) {
40 return TimeSlotServiceHandler::sendError($timeSlotService, $slot, $timeZone);
41 }
42
43 $availableSpots = $timeSlotService->getAvailableSpots($startDate, $timeZone);
44
45 if(is_wp_error($availableSpots)) {
46 return [
47 'available_slots' => [],
48 'timezone' => $timeZone,
49 'invalid_dates' => true,
50 'max_lookup_date' => $slot->getMaxLookUpDate(),
51 ];
52 }
53
54 return [
55 'available_slots' => array_filter($availableSpots),
56 'timezone' => $timeZone,
57 'max_lookup_date' => $slot->getMaxLookUpDate(),
58 ];
59 }
60
61 public function createBooking(Request $request, $eventId)
62 {
63 $calendarEvent = CalendarSlot::findOrfail($eventId);
64
65 if ($calendarEvent->status != 'active') {
66 return $this->sendError([
67 'message' => __('Sorry, the host is not accepting any new bookings at the moment.', 'fluent-booking')
68 ]);
69 }
70
71 $postedData = $request->all();
72
73 $rules = [
74 'name' => 'required',
75 'email' => 'required|email',
76 'timezone' => 'required',
77 'event_time' => 'required'
78 ];
79
80 $messages = [
81 'name.required' => __('Please enter attendee\'s name', 'fluent-booking'),
82 'email.required' => __('Please enter attendee\'s email address', 'fluent-booking'),
83 'email.email' => __('Please provide a valid email address', 'fluent-booking'),
84 'timezone.required' => __('Please select the timezone', 'fluent-booking'),
85 'event_time.required' => __('Please select a date and time', 'fluent-booking')
86 ];
87
88 $locationType = Arr::get($postedData, 'location_type');
89
90 if ($calendarEvent->isPhoneRequired()) {
91 $rules['location_description'] = 'required';
92 $messages['location_description.required'] = __('Please provide attendee\'s phone number', 'fluent-booking');
93 } else if ($calendarEvent->isAddressRequired()) {
94 $rules['location_description'] = 'required';
95 $messages['location_description.required'] = __('Please provide attendee\'s address', 'fluent-booking');
96 }
97
98 if ($additionalGuests = Arr::get($postedData, 'guests', [])) {
99 if ($calendarEvent->isMultiGuestEvent()) {
100 $additionalGuests = $this->sanitize_mapped_data($additionalGuests);
101 $additionalGuests = array_values(array_filter($additionalGuests, function ($guest) {
102 return Arr::get($guest, 'name') && Arr::get($guest, 'email');
103 }));
104 } else {
105 $additionalGuests = array_filter(array_map('sanitize_email', $additionalGuests));
106 }
107 }
108
109 $postedData['guests'] = $additionalGuests;
110
111 $requiredFields = array_filter($calendarEvent->getMeta('booking_fields', []), function ($field) {
112 return Arr::isTrue($field, 'required') && Arr::isTrue($field, 'enabled') && (Arr::get($field, 'name') == 'message' || Arr::get($field, 'name') == 'guests');
113 });
114
115 foreach ($requiredFields as $field) {
116 if (empty($rules[$field['name']])) {
117 $rules[$field['name']] = 'required';
118 $messages[$field['name'] . '.required'] = __('This field is required', 'fluent-booking');
119 }
120 }
121
122 $validationConfig = apply_filters('fluent_booking/schedule_validation_rules_data', [
123 'rules' => $rules,
124 'messages' => $messages
125 ], $postedData, $calendarEvent);
126
127 $app = App::getInstance();
128
129 $validator = $app->validator->make($postedData, $validationConfig['rules'], $validationConfig['messages']);
130 if ($validator->validate()->fails()) {
131 wp_send_json([
132 'message' => __('Please fill up the required data', 'fluent-booking'),
133 'errors' => $validator->errors()
134 ], 422);
135 return;
136 }
137
138 $customFieldsData = BookingFieldService::getCustomFieldsData(Arr::get($postedData, 'custom_fields', []), $calendarEvent);
139 $customFieldsData = apply_filters('fluent_booking/schedule_custom_field_data', $customFieldsData, $calendarEvent);
140
141 if (is_wp_error($customFieldsData)) {
142 wp_send_json([
143 'message' => $customFieldsData->get_error_message(),
144 'errors' => $customFieldsData->get_error_data()
145 ], 422);
146 return;
147 }
148
149 $duration = $calendarEvent->getDuration(Arr::get($postedData, 'duration', null));
150 $timezone = Arr::get($postedData, 'timezone', 'UTC');
151
152 $startDateTime = DateTimeHelper::convertToUtc($postedData['event_time'], $timezone);
153 $endDateTime = gmdate('Y-m-d H:i:s', strtotime($startDateTime) + ($duration * 60));
154
155 $bookingData = [
156 'person_time_zone' => sanitize_text_field($timezone),
157 'start_time' => $startDateTime,
158 'name' => sanitize_text_field($postedData['name']),
159 'email' => sanitize_email($postedData['email']),
160 'message' => sanitize_textarea_field(wp_unslash(Arr::get($postedData, 'message', ''))),
161 'phone' => sanitize_textarea_field(Arr::get($postedData, 'phone_number', '')),
162 'address' => sanitize_textarea_field(Arr::get($postedData, 'address', '')),
163 'ip_address' => Helper::getIp(),
164 'status' => sanitize_text_field($postedData['status']),
165 'source' => 'admin',
166 'event_type' => $calendarEvent->event_type,
167 'slot_minutes' => $duration
168 ];
169
170 $eventLocations = [];
171 $locationSettings = $calendarEvent->location_settings;
172 foreach ($locationSettings as $index => $location) {
173 $eventLocations[$location['type']] = $location;
174 }
175
176 $locationDetails['type'] = $locationType;
177 if ($locationType == 'phone_organizer') {
178 $locationDetails['description'] = $eventLocations[$locationType]['host_phone_number'];
179 } else if ($locationType == 'phone_guest') {
180 $bookingData['phone'] = Arr::get($postedData, 'location_description', '');
181 } else if ($locationType == 'in_person_guest') {
182 $locationDetails['description'] = Arr::get($postedData, 'location_description', '');
183 } else if (in_array($locationType, ['custom', 'in_person_organizer'])) {
184 $locationDetails['description'] = $eventLocations[$locationType]['description'];
185 } else if (in_array($locationType, ['google_meet', 'online_meeting', 'zoom_meeting', 'ms_teams'])) {
186 $locationDetails['description'] = Arr::get($eventLocations[$locationType], 'meeting_link', '');
187 }
188
189 $bookingData['location_details'] = $locationDetails;
190
191 if ($sourceUrl = Arr::get($postedData, 'source_url', '')) {
192 $bookingData['source_url'] = sanitize_url($sourceUrl);
193 }
194
195 if ($hostUserId = Arr::get($postedData, 'host_user_id', null)) {
196 $bookingData['host_user_id'] = (int)$hostUserId;
197 }
198
199 $hostIds = null;
200 if ($calendarEvent->isRoundRobin() && !$hostUserId) {
201 $hostIds = $calendarEvent->getHostIdsSortedByBookings($startDateTime);
202 $bookingData['host_user_id'] = $hostIds[0];
203 }
204
205 $availableSpot = false;
206 if (!Arr::isTrue($postedData, 'ignore_availability')) {
207 $timeSlotService = TimeSlotServiceHandler::initService($calendarEvent->calendar, $calendarEvent);
208
209 if (is_wp_error($timeSlotService)) {
210 return TimeSlotServiceHandler::sendError($timeSlotService, $calendarEvent, $timezone);
211 }
212
213 $availableSpot = $timeSlotService->isSpotAvailable($startDateTime, $endDateTime, $duration, $hostUserId);
214
215 if (!$availableSpot) {
216 wp_send_json([
217 'message' => __('This selected time slot is not available. Maybe someone booked the spot just a few seconds ago.', 'fluent-booking')
218 ], 422);
219 }
220
221 if ($calendarEvent->isRoundRobin() && !$hostUserId) {
222 $bookingData['host_user_id'] = $timeSlotService->hostUserId;
223 }
224 }
225
226 if ($additionalGuests) {
227 $guestField = BookingFieldService::getBookingFieldByName($calendarEvent, 'guests');
228 $guestLimit = Arr::get($guestField, 'limit', 10);
229 if ($calendarEvent->isMultiGuestEvent() && $availableSpot) {
230 $remaining = Arr::get($availableSpot, 'remaining', $calendarEvent->getMaxBookingPerSlot());
231 $guestLimit = min($remaining, $guestLimit) - 1;
232 }
233 $bookingData['additional_guests'] = array_slice($additionalGuests, 0, $guestLimit);
234 }
235
236 do_action('fluent_booking/before_creating_schedule', $bookingData, $postedData, $calendarEvent);
237
238 try {
239 $booking = BookingService::createBooking($bookingData, $calendarEvent, $customFieldsData);
240
241 do_action('fluent_booking/after_creating_schedule', $booking, $postedData, $calendarEvent);
242 } catch (\Exception $e) {
243 wp_send_json([
244 'message' => $e->getMessage()
245 ], $e->getCode());
246 }
247
248 return [
249 'booking' => $booking,
250 'message' => __('Booking has been created', 'fluent-booking'),
251 ];
252 }
253
254 public function getEvent(Request $request, $eventId)
255 {
256 $calendarEvent = CalendarSlot::find($eventId);
257
258 if (!$calendarEvent || $calendarEvent->status != 'active') {
259 wp_send_json([
260 'message' => __('Sorry, the host is not accepting any new bookings at the moment.', 'fluent-booking')
261 ], 422);
262 }
263
264 $calendar = $calendarEvent->calendar;
265
266 if (!$calendar) {
267 return $this->sendError([
268 'message' => __('Calendar not found', 'fluent-booking')
269 ]);
270 }
271
272 $calendarEventVars = (new FrontEndHandler())->getCalendarEventVars($calendar, $calendarEvent);
273
274 $startDate = $request->get('start_date');
275
276 if (!$startDate) {
277 $startDate = gmdate('Y-m-d H:i:s');
278 }
279
280 $timeZone = $request->get('timezone');
281
282 if (!$timeZone) {
283 $timeZone = wp_timezone_string();
284 }
285
286 if (!in_array($timeZone, \DateTimeZone::listIdentifiers())) {
287 $timeZone = $calendar->author_timezone;
288 }
289
290 $duration = $calendarEvent->getDuration($request->get('duration'));
291
292 $hostId = $request->get('host_id', null);
293
294 $timeSlotService = TimeSlotServiceHandler::initService($calendar, $calendarEvent);
295
296 if (is_wp_error($timeSlotService)) {
297 return TimeSlotServiceHandler::sendError($timeSlotService, $calendarEvent, $timeZone);
298 }
299
300 $availableSpots = $timeSlotService->getAvailableSpots($startDate, $timeZone, $duration, $hostId);
301
302 if (is_wp_error($availableSpots)) {
303 wp_send_json([
304 'available_slots' => [],
305 'calendar_event' => $calendarEventVars,
306 'timezone' => $timeZone,
307 'invalid_dates' => true
308 ], 200);
309 }
310
311 $availableSpots = apply_filters('fluent_booking/available_slots_for_view', array_filter($availableSpots), $calendarEvent, $calendar, $timeZone, $duration);
312
313 return [
314 'calendar_event' => $calendarEventVars,
315 'available_slots' => $availableSpots
316 ];
317 }
318
319 public function getBookings(Request $request)
320 {
321 $userData = get_userdata(get_current_user_id());
322
323 $userEmail = $userData ? $userData->user_email : null;
324
325 if (!$userEmail) {
326 return $this->sendError([
327 'message' => __('Please login to view your bookings', 'fluent-booking')
328 ]);
329 }
330
331 $perPage = intval($request->get('per_page', 10));
332
333 $bookingPeriod = sanitize_text_field($request->get('period', 'all'));
334
335 $bookingQuery = Booking::query()->with('calendar_event')
336 ->where('email', $userEmail)
337 ->applyComputedStatus($bookingPeriod)
338 ->applyBookingOrderByStatus($bookingPeriod);
339
340 $calendarIds = $request->get('calendar_ids', []);
341
342 if (!in_array('all', $calendarIds)) {
343 $calendarIds = array_map('intval', $calendarIds);
344 $bookingQuery->whereIn('calendar_id', $calendarIds);
345 }
346
347 do_action_ref_array('fluent_booking/bookings_query', [&$bookingQuery]);
348
349 $totalBookings = $bookingQuery->count();
350
351 $bookings = $bookingQuery->limit($perPage)->get();
352
353 $formattedBookings = [];
354 foreach ($bookings as $booking) {
355 $formattedBookings[] = [
356 'id' => $booking->id,
357 'person_time_zone' => $booking->person_time_zone,
358 'status' => ucfirst($booking->status),
359 'payment_status' => $booking->payment_status,
360 'booking_title' => $booking->getBookingTitle(true),
361 'author_name' => $booking->getHostDetails(false)['name'],
362 'booking_date' => DateTimeHelper::formatToLocale($booking->getAttendeeStartTime(), 'date'),
363 'booking_time' => DateTimeHelper::formatToLocale($booking->getAttendeeEndTime(), 'time') . ' - ' . DateTimeHelper::formatToLocale($booking->getAttendeeEndTime(), 'time'),
364 ];
365 }
366
367 return [
368 'bookings' => $formattedBookings,
369 'total' => $totalBookings
370 ];
371 }
372
373 private static function sanitize_mapped_data($settings)
374 {
375 $sanitizerMap = [
376 'name' => 'sanitize_text_field',
377 'email' => 'sanitize_email',
378 ];
379
380 return Helper::fcal_backend_sanitizer($settings, $sanitizerMap);
381 }
382 }
383