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

385 lines 15.5 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 $additionalGuests = Arr::get($postedData, 'guests', []);
99 if (!empty($additionalGuests)) {
100 if ($calendarEvent->isMultiGuestEvent()) {
101 $additionalGuests = $this->sanitize_mapped_data($additionalGuests);
102 $additionalGuests = array_values(array_filter($additionalGuests, function ($guest) {
103 return Arr::get($guest, 'name') && Arr::get($guest, 'email');
104 }));
105 } else {
106 $additionalGuests = array_filter(array_map('sanitize_email', $additionalGuests));
107 }
108 }
109
110 $postedData['guests'] = $additionalGuests;
111
112 $requiredFields = array_filter($calendarEvent->getMeta('booking_fields', []), function ($field) {
113 return Arr::isTrue($field, 'required') && Arr::isTrue($field, 'enabled') && (Arr::get($field, 'name') == 'message' || Arr::get($field, 'name') == 'guests');
114 });
115
116 foreach ($requiredFields as $field) {
117 if (empty($rules[$field['name']])) {
118 $rules[$field['name']] = 'required';
119 $messages[$field['name'] . '.required'] = __('This field is required', 'fluent-booking');
120 }
121 }
122
123 $validationConfig = apply_filters('fluent_booking/schedule_validation_rules_data', [
124 'rules' => $rules,
125 'messages' => $messages
126 ], $postedData, $calendarEvent);
127
128 $app = App::getInstance();
129
130 $validator = $app->validator->make($postedData, $validationConfig['rules'], $validationConfig['messages']);
131 if ($validator->validate()->fails()) {
132 wp_send_json([
133 'message' => __('Please fill up the required data', 'fluent-booking'),
134 'errors' => $validator->errors()
135 ], 422);
136 return;
137 }
138
139 $customFieldsData = BookingFieldService::getCustomFieldsData(Arr::get($postedData, 'custom_fields', []), $calendarEvent);
140 $customFieldsData = apply_filters('fluent_booking/schedule_custom_field_data', $customFieldsData, $calendarEvent);
141
142 if (is_wp_error($customFieldsData)) {
143 wp_send_json([
144 'message' => $customFieldsData->get_error_message(),
145 'errors' => $customFieldsData->get_error_data()
146 ], 422);
147 return;
148 }
149
150 $duration = $calendarEvent->getDuration(Arr::get($postedData, 'duration', null));
151 $timezone = sanitize_text_field($postedData['timezone']);
152
153 $startDateTime = DateTimeHelper::convertToUtc(sanitize_text_field($postedData['event_time']), $timezone);
154 $endDateTime = gmdate('Y-m-d H:i:s', strtotime($startDateTime) + ($duration * 60));
155
156 $bookingData = apply_filters('fluent_booking/initialize_booking_data', [
157 'person_time_zone' => $timezone,
158 'start_time' => $startDateTime,
159 'name' => sanitize_text_field($postedData['name']),
160 'email' => sanitize_email($postedData['email']),
161 'message' => sanitize_textarea_field(wp_unslash(Arr::get($postedData, 'message', ''))),
162 'phone' => sanitize_text_field(Arr::get($postedData, 'phone_number', '')),
163 'address' => sanitize_textarea_field(Arr::get($postedData, 'address', '')),
164 'ip_address' => Helper::getIp(),
165 'status' => sanitize_text_field(Arr::get($postedData, 'status', 'scheduled')),
166 'source' => Arr::get($postedData, 'source') == 'admin' ? 'admin' : 'web',
167 'event_type' => $calendarEvent->event_type,
168 'slot_minutes' => $duration
169 ], $postedData, $calendarEvent);
170
171 $eventLocations = [];
172 $locationSettings = $calendarEvent->location_settings;
173 foreach ($locationSettings as $index => $location) {
174 $eventLocations[$location['type']] = $location;
175 }
176
177 $locationDetails['type'] = $locationType;
178 if ($locationType == 'phone_organizer') {
179 $locationDetails['description'] = $eventLocations[$locationType]['host_phone_number'];
180 } else if ($locationType == 'phone_guest') {
181 $bookingData['phone'] = sanitize_text_field(Arr::get($postedData, 'location_description', ''));
182 } else if ($locationType == 'in_person_guest') {
183 $locationDetails['description'] = sanitize_textarea_field(Arr::get($postedData, 'location_description', ''));
184 } else if (in_array($locationType, ['custom', 'in_person_organizer'])) {
185 $locationDetails['description'] = $eventLocations[$locationType]['description'];
186 } else if (in_array($locationType, ['google_meet', 'online_meeting', 'zoom_meeting', 'ms_teams'])) {
187 $locationDetails['description'] = Arr::get($eventLocations[$locationType], 'meeting_link', '');
188 $locationDetails['online_platform_link'] = $locationDetails['description'];
189 }
190
191 $bookingData['location_details'] = $locationDetails;
192
193 if ($sourceUrl = Arr::get($postedData, 'source_url', '')) {
194 $bookingData['source_url'] = sanitize_url($sourceUrl);
195 }
196
197 if ($hostUserId = Arr::get($postedData, 'host_user_id', null)) {
198 $bookingData['host_user_id'] = (int) $hostUserId;
199 }
200
201 $hostIds = null;
202 if ($calendarEvent->isRoundRobin() && !$hostUserId) {
203 $hostIds = $calendarEvent->getHostIdsSortedByBookings($startDateTime);
204 $bookingData['host_user_id'] = $hostIds[0];
205 }
206
207 $availableSpot = false;
208 if (!Arr::isTrue($postedData, 'ignore_availability')) {
209 $timeSlotService = TimeSlotServiceHandler::initService($calendarEvent->calendar, $calendarEvent);
210
211 if (is_wp_error($timeSlotService)) {
212 return TimeSlotServiceHandler::sendError($timeSlotService, $calendarEvent, $timezone);
213 }
214
215 $availableSpot = $timeSlotService->isSpotAvailable($startDateTime, $endDateTime, $duration, $hostUserId);
216
217 if (!$availableSpot) {
218 wp_send_json([
219 'message' => __('This selected time slot is not available. Maybe someone booked the spot just a few seconds ago.', 'fluent-booking')
220 ], 422);
221 }
222
223 if ($calendarEvent->isRoundRobin() && !$hostUserId) {
224 $bookingData['host_user_id'] = $timeSlotService->hostUserId;
225 }
226 }
227
228 if ($additionalGuests) {
229 $guestField = BookingFieldService::getBookingFieldByName($calendarEvent, 'guests');
230 $guestLimit = Arr::get($guestField, 'limit', 10);
231 if ($calendarEvent->isMultiGuestEvent() && $availableSpot) {
232 $remaining = Arr::get($availableSpot, 'remaining', $calendarEvent->getMaxBookingPerSlot());
233 $guestLimit = min($remaining, $guestLimit) - 1;
234 }
235 $bookingData['additional_guests'] = array_slice($additionalGuests, 0, $guestLimit);
236 }
237
238 do_action('fluent_booking/before_creating_schedule', $bookingData, $postedData, $calendarEvent);
239
240 try {
241 $booking = BookingService::createBooking($bookingData, $calendarEvent, $customFieldsData);
242
243 do_action('fluent_booking/after_creating_schedule', $booking, $postedData, $calendarEvent);
244 } catch (\Exception $e) {
245 wp_send_json([
246 'message' => $e->getMessage()
247 ], $e->getCode());
248 }
249
250 return [
251 'booking' => $booking,
252 'message' => __('Booking has been created', 'fluent-booking'),
253 ];
254 }
255
256 public function getEvent(Request $request, $eventId)
257 {
258 $calendarEvent = CalendarSlot::find($eventId);
259
260 if (!$calendarEvent || $calendarEvent->status != 'active') {
261 wp_send_json([
262 'message' => __('Sorry, the host is not accepting any new bookings at the moment.', 'fluent-booking')
263 ], 422);
264 }
265
266 $calendar = $calendarEvent->calendar;
267
268 if (!$calendar) {
269 return $this->sendError([
270 'message' => __('Calendar not found', 'fluent-booking')
271 ]);
272 }
273
274 $calendarEventVars = (new FrontEndHandler())->getCalendarEventVars($calendar, $calendarEvent);
275
276 $startDate = $request->get('start_date');
277
278 if (!$startDate) {
279 $startDate = gmdate('Y-m-d H:i:s');
280 }
281
282 $timeZone = $request->get('timezone');
283
284 if (!$timeZone) {
285 $timeZone = wp_timezone_string();
286 }
287
288 if (!in_array($timeZone, \DateTimeZone::listIdentifiers())) {
289 $timeZone = $calendar->author_timezone;
290 }
291
292 $duration = $calendarEvent->getDuration($request->get('duration'));
293
294 $hostId = $request->get('host_id', null);
295
296 $timeSlotService = TimeSlotServiceHandler::initService($calendar, $calendarEvent);
297
298 if (is_wp_error($timeSlotService)) {
299 return TimeSlotServiceHandler::sendError($timeSlotService, $calendarEvent, $timeZone);
300 }
301
302 $availableSpots = $timeSlotService->getAvailableSpots($startDate, $timeZone, $duration, $hostId);
303
304 if (is_wp_error($availableSpots)) {
305 wp_send_json([
306 'available_slots' => [],
307 'calendar_event' => $calendarEventVars,
308 'timezone' => $timeZone,
309 'invalid_dates' => true
310 ], 200);
311 }
312
313 $availableSpots = apply_filters('fluent_booking/available_slots_for_view', array_filter($availableSpots), $calendarEvent, $calendar, $timeZone, $duration);
314
315 return [
316 'calendar_event' => $calendarEventVars,
317 'available_slots' => $availableSpots
318 ];
319 }
320
321 public function getBookings(Request $request)
322 {
323 $userData = get_userdata(get_current_user_id());
324
325 $userEmail = $userData ? $userData->user_email : null;
326
327 if (!$userEmail) {
328 return $this->sendError([
329 'message' => __('Please login to view your bookings', 'fluent-booking')
330 ]);
331 }
332
333 $perPage = intval($request->get('per_page', 10));
334
335 $bookingPeriod = sanitize_text_field($request->get('period', 'all'));
336
337 $bookingQuery = Booking::query()->with('calendar_event')
338 ->where('email', $userEmail)
339 ->applyComputedStatus($bookingPeriod)
340 ->applyBookingOrderByStatus($bookingPeriod);
341
342 $calendarIds = $request->get('calendar_ids', []);
343
344 if (!in_array('all', $calendarIds)) {
345 $calendarIds = array_map('intval', $calendarIds);
346 $bookingQuery->whereIn('calendar_id', $calendarIds);
347 }
348
349 do_action_ref_array('fluent_booking/bookings_query', [&$bookingQuery]);
350
351 $totalBookings = $bookingQuery->count();
352
353 $bookings = $bookingQuery->limit($perPage)->get();
354
355 $formattedBookings = [];
356 foreach ($bookings as $booking) {
357 $formattedBookings[] = [
358 'id' => $booking->id,
359 'person_time_zone' => $booking->person_time_zone,
360 'status' => ucfirst($booking->status),
361 'payment_status' => $booking->payment_status,
362 'booking_title' => $booking->getBookingTitle(true),
363 'author_name' => $booking->getHostDetails(false)['name'],
364 'booking_date' => DateTimeHelper::formatToLocale($booking->getAttendeeStartTime(), 'date'),
365 'booking_time' => DateTimeHelper::formatToLocale($booking->getAttendeeStartTime(), 'time') . ' - ' . DateTimeHelper::formatToLocale($booking->getAttendeeEndTime(), 'time'),
366 ];
367 }
368
369 return [
370 'bookings' => $formattedBookings,
371 'total' => $totalBookings
372 ];
373 }
374
375 private static function sanitize_mapped_data($settings)
376 {
377 $sanitizerMap = [
378 'name' => 'sanitize_text_field',
379 'email' => 'sanitize_email',
380 ];
381
382 return Helper::fcal_backend_sanitizer($settings, $sanitizerMap);
383 }
384 }
385