PluginProbe
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution / 2.5.0
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution v2.5.0
2.5.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 All 34 releases
← All changes | app/Http/Controllers/BookingController.php +130 -15 1.7.0 → 2.5.0 View file →
@@ -5,8 +5,9 @@
5 5 use FluentBooking\App\App;
6 6 use FluentBooking\App\Models\Booking;
7 7 use FluentBooking\App\Models\CalendarSlot;
8 8 use FluentBooking\App\Services\BookingService;
9 +use FluentBooking\App\Services\RescheduleService;
9 10 use FluentBooking\App\Services\DateTimeHelper;
10 11 use FluentBooking\App\Services\BookingFieldService;
11 12 use FluentBooking\App\Hooks\Handlers\FrontEndHandler;
12 13 use FluentBooking\App\Hooks\Handlers\TimeSlotServiceHandler;
@@ -87,16 +88,19 @@
87 88
88 89 $locationType = Arr::get($postedData, 'location_type');
89 90
90 91 if ($calendarEvent->isPhoneRequired()) {
91 - $rules['location_description'] = 'required';
92 + $rules['location_description'] = ['required', $this->validPhoneNumberRule()];
92 93 $messages['location_description.required'] = __('Please provide attendee\'s phone number', 'fluent-booking');
93 94 } else if ($calendarEvent->isAddressRequired()) {
94 95 $rules['location_description'] = 'required';
95 96 $messages['location_description.required'] = __('Please provide attendee\'s address', 'fluent-booking');
97 + } else if ($locationType === 'phone_guest') {
98 + $rules['location_description'] = [$this->validPhoneNumberRule()];
96 99 }
97 100
98 - if ($additionalGuests = Arr::get($postedData, 'guests', [])) {
101 + $additionalGuests = Arr::get($postedData, 'guests', []);
102 + if (!empty($additionalGuests)) {
99 103 if ($calendarEvent->isMultiGuestEvent()) {
100 104 $additionalGuests = $this->sanitize_mapped_data($additionalGuests);
101 105 $additionalGuests = array_values(array_filter($additionalGuests, function ($guest) {
102 106 return Arr::get($guest, 'name') && Arr::get($guest, 'email');
@@ -146,27 +150,27 @@
146 150 return;
147 151 }
148 152
149 153 $duration = $calendarEvent->getDuration(Arr::get($postedData, 'duration', null));
150 - $timezone = Arr::get($postedData, 'timezone', 'UTC');
154 + $timezone = sanitize_text_field($postedData['timezone']);
151 155
152 - $startDateTime = DateTimeHelper::convertToUtc($postedData['event_time'], $timezone);
156 + $startDateTime = DateTimeHelper::convertToUtc(sanitize_text_field($postedData['event_time']), $timezone);
153 157 $endDateTime = gmdate('Y-m-d H:i:s', strtotime($startDateTime) + ($duration * 60));
154 158
155 - $bookingData = [
156 - 'person_time_zone' => sanitize_text_field($timezone),
159 + $bookingData = apply_filters('fluent_booking/initialize_booking_data', [
160 + 'person_time_zone' => $timezone,
157 161 'start_time' => $startDateTime,
158 162 'name' => sanitize_text_field($postedData['name']),
159 163 'email' => sanitize_email($postedData['email']),
160 164 'message' => sanitize_textarea_field(wp_unslash(Arr::get($postedData, 'message', ''))),
161 - 'phone' => sanitize_textarea_field(Arr::get($postedData, 'phone_number', '')),
165 + 'phone' => sanitize_text_field(Arr::get($postedData, 'phone_number', '')),
162 166 'address' => sanitize_textarea_field(Arr::get($postedData, 'address', '')),
163 167 'ip_address' => Helper::getIp(),
164 - 'status' => sanitize_text_field($postedData['status']),
165 - 'source' => 'admin',
168 + 'status' => sanitize_text_field(Arr::get($postedData, 'status', 'scheduled')),
169 + 'source' => Arr::get($postedData, 'source') == 'admin' ? 'admin' : 'web',
166 170 'event_type' => $calendarEvent->event_type,
167 171 'slot_minutes' => $duration
168 - ];
172 + ], $postedData, $calendarEvent);
169 173
170 174 $eventLocations = [];
171 175 $locationSettings = $calendarEvent->location_settings;
172 176 foreach ($locationSettings as $index => $location) {
@@ -176,11 +180,11 @@
176 180 $locationDetails['type'] = $locationType;
177 181 if ($locationType == 'phone_organizer') {
178 182 $locationDetails['description'] = $eventLocations[$locationType]['host_phone_number'];
179 183 } else if ($locationType == 'phone_guest') {
180 - $bookingData['phone'] = Arr::get($postedData, 'location_description', '');
184 + $bookingData['phone'] = sanitize_text_field(Arr::get($postedData, 'location_description', ''));
181 185 } else if ($locationType == 'in_person_guest') {
182 - $locationDetails['description'] = Arr::get($postedData, 'location_description', '');
186 + $locationDetails['description'] = sanitize_textarea_field(Arr::get($postedData, 'location_description', ''));
183 187 } else if (in_array($locationType, ['custom', 'in_person_organizer'])) {
184 188 $locationDetails['description'] = $eventLocations[$locationType]['description'];
185 189 } else if (in_array($locationType, ['google_meet', 'online_meeting', 'zoom_meeting', 'ms_teams'])) {
186 190 $locationDetails['description'] = Arr::get($eventLocations[$locationType], 'meeting_link', '');
@@ -193,9 +197,17 @@
193 197 $bookingData['source_url'] = sanitize_url($sourceUrl);
194 198 }
195 199
196 200 if ($hostUserId = Arr::get($postedData, 'host_user_id', null)) {
197 - $bookingData['host_user_id'] = (int)$hostUserId;
201 + $hostUserId = (int) $hostUserId;
202 +
203 + if (!in_array($hostUserId, array_map('intval', $calendarEvent->getHostIds()), true)) {
204 + return $this->sendError([
205 + 'message' => __('The selected host is not a host of this event', 'fluent-booking')
206 + ], 422);
207 + }
208 +
209 + $bookingData['host_user_id'] = $hostUserId;
198 210 }
199 211
200 212 $hostIds = null;
201 213 if ($calendarEvent->isRoundRobin() && !$hostUserId) {
@@ -251,8 +263,20 @@
251 263 'message' => __('Booking has been created', 'fluent-booking'),
252 264 ];
253 265 }
254 266
267 + /**
268 + * @return \Closure
269 + */
270 + private function validPhoneNumberRule()
271 + {
272 + return function ($attribute, $value) {
273 + if (!empty($value) && !Helper::isValidPhoneNumber($value)) {
274 + return __('Please provide a valid phone number', 'fluent-booking');
275 + }
276 + };
277 + }
278 +
255 279 public function getEvent(Request $request, $eventId)
256 280 {
257 281 $calendarEvent = CalendarSlot::find($eventId);
258 282
@@ -290,9 +314,19 @@
290 314
291 315 $duration = $calendarEvent->getDuration($request->get('duration'));
292 316
293 317 $hostId = $request->get('host_id', null);
294 -
318 +
319 + if ($hostId) {
320 + $hostId = (int) $hostId;
321 +
322 + if (!in_array($hostId, array_map('intval', $calendarEvent->getHostIds()), true)) {
323 + wp_send_json([
324 + 'message' => __('The selected host is not a host of this event', 'fluent-booking')
325 + ], 422);
326 + }
327 + }
328 +
295 329 $timeSlotService = TimeSlotServiceHandler::initService($calendar, $calendarEvent);
296 330
297 331 if (is_wp_error($timeSlotService)) {
298 332 return TimeSlotServiceHandler::sendError($timeSlotService, $calendarEvent, $timeZone);
@@ -360,9 +394,9 @@
360 394 'payment_status' => $booking->payment_status,
361 395 'booking_title' => $booking->getBookingTitle(true),
362 396 'author_name' => $booking->getHostDetails(false)['name'],
363 397 'booking_date' => DateTimeHelper::formatToLocale($booking->getAttendeeStartTime(), 'date'),
364 - 'booking_time' => DateTimeHelper::formatToLocale($booking->getAttendeeEndTime(), 'time') . ' - ' . DateTimeHelper::formatToLocale($booking->getAttendeeEndTime(), 'time'),
398 + 'booking_time' => DateTimeHelper::formatToLocale($booking->getAttendeeStartTime(), 'time') . ' - ' . DateTimeHelper::formatToLocale($booking->getAttendeeEndTime(), 'time'),
365 399 ];
366 400 }
367 401
368 402 return [
@@ -367,8 +401,89 @@
367 401
368 402 return [
369 403 'bookings' => $formattedBookings,
370 404 'total' => $totalBookings
405 + ];
406 + }
407 +
408 + public function getRescheduleSlots(Request $request, $eventId, $bookingId)
409 + {
410 + return $this->getEvent($request, $eventId);
411 + }
412 +
413 + public function rescheduleBooking(Request $request, $eventId, $bookingId)
414 + {
415 + $booking = Booking::with(['calendar_event'])->findOrFail($bookingId);
416 +
417 + $data = $request->all();
418 +
419 + $this->validate($data, [
420 + 'event_time' => 'required',
421 + 'timezone' => 'required'
422 + ], [
423 + 'event_time.required' => __('Please select a date and time', 'fluent-booking'),
424 + 'timezone.required' => __('Please select the timezone', 'fluent-booking')
425 + ]);
426 +
427 + $calendarEvent = $booking->calendar_event;
428 +
429 + if (!$calendarEvent || (int) $calendarEvent->id !== (int) $eventId) {
430 + return $this->sendError(['message' => __('The event of this booking was not found', 'fluent-booking')], 422);
431 + }
432 +
433 + if (!in_array($booking->status, ['scheduled', 'pending', 'approved', 'rescheduled'], true)) {
434 + return $this->sendError(['message' => __('This booking can not be rescheduled', 'fluent-booking')], 422);
435 + }
436 +
437 + $timezone = sanitize_text_field(Arr::get($data, 'timezone'));
438 +
439 + if (!in_array($timezone, \DateTimeZone::listIdentifiers(), true)) {
440 + return $this->sendError(['message' => __('Please select a valid timezone', 'fluent-booking')], 422);
441 + }
442 +
443 + $eventTime = sanitize_text_field(Arr::get($data, 'event_time'));
444 +
445 + if (!\DateTime::createFromFormat('Y-m-d H:i:s', $eventTime)) {
446 + return $this->sendError(['message' => __('Please select a valid date and time', 'fluent-booking')], 422);
447 + }
448 +
449 + $startTime = DateTimeHelper::convertToUtc($eventTime, $timezone);
450 + $endTime = gmdate('Y-m-d H:i:s', strtotime($startTime) + ($booking->slot_minutes * 60)); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
451 +
452 + $hostUserId = null;
453 +
454 + if (!Arr::isTrue($data, 'ignore_availability')) {
455 + $timeSlotService = TimeSlotServiceHandler::initService($calendarEvent->calendar, $calendarEvent);
456 +
457 + if (is_wp_error($timeSlotService)) {
458 + return $this->sendError(['message' => $timeSlotService->get_error_message()], 422);
459 + }
460 +
461 + $isSlotLocked = Helper::lockRoundRobinSlot($calendarEvent, $startTime, $endTime);
462 +
463 + if (!$isSlotLocked || !$timeSlotService->isSpotAvailable($startTime, $endTime, $booking->slot_minutes)) {
464 + return $this->sendError([
465 + 'message' => __('This selected time slot is not available. Maybe someone booked the spot just a few seconds ago.', 'fluent-booking')
466 + ], 422);
467 + }
468 +
469 + if ($calendarEvent->isRoundRobin()) {
470 + $hostUserId = $timeSlotService->hostUserId;
471 + }
472 + }
473 +
474 + $result = RescheduleService::reschedule($booking, $calendarEvent, $startTime, $timezone, [
475 + 'reason' => Arr::get($data, 'reason', ''),
476 + 'host_user_id' => $hostUserId,
477 + 'source' => __('Admin Panel', 'fluent-booking')
478 + ]);
479 +
480 + if (is_wp_error($result)) {
481 + return $this->sendError(['message' => $result->get_error_message()], 422);
482 + }
483 +
484 + return [
485 + 'message' => __('Booking has been rescheduled', 'fluent-booking')
371 486 ];
372 487 }
373 488
374 489 private static function sanitize_mapped_data($settings)