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

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