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

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