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

417 lines 16.6 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 if ($hostId) {
319 $hostId = (int) $hostId;
320
321 if (!in_array($hostId, array_map('intval', $calendarEvent->getHostIds()), true)) {
322 wp_send_json([
323 'message' => __('The selected host is not a host of this event', 'fluent-booking')
324 ], 422);
325 }
326 }
327
328 $timeSlotService = TimeSlotServiceHandler::initService($calendar, $calendarEvent);
329
330 if (is_wp_error($timeSlotService)) {
331 return TimeSlotServiceHandler::sendError($timeSlotService, $calendarEvent, $timeZone);
332 }
333
334 $availableSpots = $timeSlotService->getAvailableSpots($startDate, $timeZone, $duration, $hostId);
335
336 if (is_wp_error($availableSpots)) {
337 wp_send_json([
338 'available_slots' => [],
339 'calendar_event' => $calendarEventVars,
340 'timezone' => $timeZone,
341 'invalid_dates' => true
342 ], 200);
343 }
344
345 $availableSpots = apply_filters('fluent_booking/available_slots_for_view', array_filter($availableSpots), $calendarEvent, $calendar, $timeZone, $duration);
346
347 return [
348 'calendar_event' => $calendarEventVars,
349 'available_slots' => $availableSpots
350 ];
351 }
352
353 public function getBookings(Request $request)
354 {
355 $userData = get_userdata(get_current_user_id());
356
357 $userEmail = $userData ? $userData->user_email : null;
358
359 if (!$userEmail) {
360 return $this->sendError([
361 'message' => __('Please login to view your bookings', 'fluent-booking')
362 ]);
363 }
364
365 $perPage = intval($request->get('per_page', 10));
366
367 $bookingPeriod = sanitize_text_field($request->get('period', 'all'));
368
369 $bookingQuery = Booking::query()->with('calendar_event')
370 ->where('email', $userEmail)
371 ->applyComputedStatus($bookingPeriod)
372 ->applyBookingOrderByStatus($bookingPeriod);
373
374 $calendarIds = $request->get('calendar_ids', []);
375
376 if (!in_array('all', $calendarIds)) {
377 $calendarIds = array_map('intval', $calendarIds);
378 $bookingQuery->whereIn('calendar_id', $calendarIds);
379 }
380
381 do_action_ref_array('fluent_booking/bookings_query', [&$bookingQuery]);
382
383 $totalBookings = $bookingQuery->count();
384
385 $bookings = $bookingQuery->limit($perPage)->get();
386
387 $formattedBookings = [];
388 foreach ($bookings as $booking) {
389 $formattedBookings[] = [
390 'id' => $booking->id,
391 'person_time_zone' => $booking->person_time_zone,
392 'status' => ucfirst($booking->status),
393 'payment_status' => $booking->payment_status,
394 'booking_title' => $booking->getBookingTitle(true),
395 'author_name' => $booking->getHostDetails(false)['name'],
396 'booking_date' => DateTimeHelper::formatToLocale($booking->getAttendeeStartTime(), 'date'),
397 'booking_time' => DateTimeHelper::formatToLocale($booking->getAttendeeStartTime(), 'time') . ' - ' . DateTimeHelper::formatToLocale($booking->getAttendeeEndTime(), 'time'),
398 ];
399 }
400
401 return [
402 'bookings' => $formattedBookings,
403 'total' => $totalBookings
404 ];
405 }
406
407 private static function sanitize_mapped_data($settings)
408 {
409 $sanitizerMap = [
410 'name' => 'sanitize_text_field',
411 'email' => 'sanitize_email',
412 ];
413
414 return Helper::fcal_backend_sanitizer($settings, $sanitizerMap);
415 }
416 }
417