PluginProbe
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution / 1.10.02
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution v1.10.02
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.10.02, at app/Http/Controllers/BookingController.php

384 lines 15.3 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 $locationDetails['online_platform_link'] = $locationDetails['description'];
188 }
189
190 $bookingData['location_details'] = $locationDetails;
191
192 if ($sourceUrl = Arr::get($postedData, 'source_url', '')) {
193 $bookingData['source_url'] = sanitize_url($sourceUrl);
194 }
195
196 if ($hostUserId = Arr::get($postedData, 'host_user_id', null)) {
197 $bookingData['host_user_id'] = (int)$hostUserId;
198 }
199
200 $hostIds = null;
201 if ($calendarEvent->isRoundRobin() && !$hostUserId) {
202 $hostIds = $calendarEvent->getHostIdsSortedByBookings($startDateTime);
203 $bookingData['host_user_id'] = $hostIds[0];
204 }
205
206 $availableSpot = false;
207 if (!Arr::isTrue($postedData, 'ignore_availability')) {
208 $timeSlotService = TimeSlotServiceHandler::initService($calendarEvent->calendar, $calendarEvent);
209
210 if (is_wp_error($timeSlotService)) {
211 return TimeSlotServiceHandler::sendError($timeSlotService, $calendarEvent, $timezone);
212 }
213
214 $availableSpot = $timeSlotService->isSpotAvailable($startDateTime, $endDateTime, $duration, $hostUserId);
215
216 if (!$availableSpot) {
217 wp_send_json([
218 'message' => __('This selected time slot is not available. Maybe someone booked the spot just a few seconds ago.', 'fluent-booking')
219 ], 422);
220 }
221
222 if ($calendarEvent->isRoundRobin() && !$hostUserId) {
223 $bookingData['host_user_id'] = $timeSlotService->hostUserId;
224 }
225 }
226
227 if ($additionalGuests) {
228 $guestField = BookingFieldService::getBookingFieldByName($calendarEvent, 'guests');
229 $guestLimit = Arr::get($guestField, 'limit', 10);
230 if ($calendarEvent->isMultiGuestEvent() && $availableSpot) {
231 $remaining = Arr::get($availableSpot, 'remaining', $calendarEvent->getMaxBookingPerSlot());
232 $guestLimit = min($remaining, $guestLimit) - 1;
233 }
234 $bookingData['additional_guests'] = array_slice($additionalGuests, 0, $guestLimit);
235 }
236
237 do_action('fluent_booking/before_creating_schedule', $bookingData, $postedData, $calendarEvent);
238
239 try {
240 $booking = BookingService::createBooking($bookingData, $calendarEvent, $customFieldsData);
241
242 do_action('fluent_booking/after_creating_schedule', $booking, $postedData, $calendarEvent);
243 } catch (\Exception $e) {
244 wp_send_json([
245 'message' => $e->getMessage()
246 ], $e->getCode());
247 }
248
249 return [
250 'booking' => $booking,
251 'message' => __('Booking has been created', 'fluent-booking'),
252 ];
253 }
254
255 public function getEvent(Request $request, $eventId)
256 {
257 $calendarEvent = CalendarSlot::find($eventId);
258
259 if (!$calendarEvent || $calendarEvent->status != 'active') {
260 wp_send_json([
261 'message' => __('Sorry, the host is not accepting any new bookings at the moment.', 'fluent-booking')
262 ], 422);
263 }
264
265 $calendar = $calendarEvent->calendar;
266
267 if (!$calendar) {
268 return $this->sendError([
269 'message' => __('Calendar not found', 'fluent-booking')
270 ]);
271 }
272
273 $calendarEventVars = (new FrontEndHandler())->getCalendarEventVars($calendar, $calendarEvent);
274
275 $startDate = $request->get('start_date');
276
277 if (!$startDate) {
278 $startDate = gmdate('Y-m-d H:i:s');
279 }
280
281 $timeZone = $request->get('timezone');
282
283 if (!$timeZone) {
284 $timeZone = wp_timezone_string();
285 }
286
287 if (!in_array($timeZone, \DateTimeZone::listIdentifiers())) {
288 $timeZone = $calendar->author_timezone;
289 }
290
291 $duration = $calendarEvent->getDuration($request->get('duration'));
292
293 $hostId = $request->get('host_id', null);
294
295 $timeSlotService = TimeSlotServiceHandler::initService($calendar, $calendarEvent);
296
297 if (is_wp_error($timeSlotService)) {
298 return TimeSlotServiceHandler::sendError($timeSlotService, $calendarEvent, $timeZone);
299 }
300
301 $availableSpots = $timeSlotService->getAvailableSpots($startDate, $timeZone, $duration, $hostId);
302
303 if (is_wp_error($availableSpots)) {
304 wp_send_json([
305 'available_slots' => [],
306 'calendar_event' => $calendarEventVars,
307 'timezone' => $timeZone,
308 'invalid_dates' => true
309 ], 200);
310 }
311
312 $availableSpots = apply_filters('fluent_booking/available_slots_for_view', array_filter($availableSpots), $calendarEvent, $calendar, $timeZone, $duration);
313
314 return [
315 'calendar_event' => $calendarEventVars,
316 'available_slots' => $availableSpots
317 ];
318 }
319
320 public function getBookings(Request $request)
321 {
322 $userData = get_userdata(get_current_user_id());
323
324 $userEmail = $userData ? $userData->user_email : null;
325
326 if (!$userEmail) {
327 return $this->sendError([
328 'message' => __('Please login to view your bookings', 'fluent-booking')
329 ]);
330 }
331
332 $perPage = intval($request->get('per_page', 10));
333
334 $bookingPeriod = sanitize_text_field($request->get('period', 'all'));
335
336 $bookingQuery = Booking::query()->with('calendar_event')
337 ->where('email', $userEmail)
338 ->applyComputedStatus($bookingPeriod)
339 ->applyBookingOrderByStatus($bookingPeriod);
340
341 $calendarIds = $request->get('calendar_ids', []);
342
343 if (!in_array('all', $calendarIds)) {
344 $calendarIds = array_map('intval', $calendarIds);
345 $bookingQuery->whereIn('calendar_id', $calendarIds);
346 }
347
348 do_action_ref_array('fluent_booking/bookings_query', [&$bookingQuery]);
349
350 $totalBookings = $bookingQuery->count();
351
352 $bookings = $bookingQuery->limit($perPage)->get();
353
354 $formattedBookings = [];
355 foreach ($bookings as $booking) {
356 $formattedBookings[] = [
357 'id' => $booking->id,
358 'person_time_zone' => $booking->person_time_zone,
359 'status' => ucfirst($booking->status),
360 'payment_status' => $booking->payment_status,
361 'booking_title' => $booking->getBookingTitle(true),
362 'author_name' => $booking->getHostDetails(false)['name'],
363 'booking_date' => DateTimeHelper::formatToLocale($booking->getAttendeeStartTime(), 'date'),
364 'booking_time' => DateTimeHelper::formatToLocale($booking->getAttendeeEndTime(), 'time') . ' - ' . DateTimeHelper::formatToLocale($booking->getAttendeeEndTime(), 'time'),
365 ];
366 }
367
368 return [
369 'bookings' => $formattedBookings,
370 'total' => $totalBookings
371 ];
372 }
373
374 private static function sanitize_mapped_data($settings)
375 {
376 $sanitizerMap = [
377 'name' => 'sanitize_text_field',
378 'email' => 'sanitize_email',
379 ];
380
381 return Helper::fcal_backend_sanitizer($settings, $sanitizerMap);
382 }
383 }
384