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 +131 -15 1.6.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,15 +180,16 @@
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', '');
191 + $locationDetails['online_platform_link'] = $locationDetails['description'];
187 192 }
188 193
189 194 $bookingData['location_details'] = $locationDetails;
190 195
@@ -192,9 +197,17 @@
192 197 $bookingData['source_url'] = sanitize_url($sourceUrl);
193 198 }
194 199
195 200 if ($hostUserId = Arr::get($postedData, 'host_user_id', null)) {
196 - $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;
197 210 }
198 211
199 212 $hostIds = null;
200 213 if ($calendarEvent->isRoundRobin() && !$hostUserId) {
@@ -250,8 +263,20 @@
250 263 'message' => __('Booking has been created', 'fluent-booking'),
251 264 ];
252 265 }
253 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 +
254 279 public function getEvent(Request $request, $eventId)
255 280 {
256 281 $calendarEvent = CalendarSlot::find($eventId);
257 282
@@ -289,9 +314,19 @@
289 314
290 315 $duration = $calendarEvent->getDuration($request->get('duration'));
291 316
292 317 $hostId = $request->get('host_id', null);
293 -
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 +
294 329 $timeSlotService = TimeSlotServiceHandler::initService($calendar, $calendarEvent);
295 330
296 331 if (is_wp_error($timeSlotService)) {
297 332 return TimeSlotServiceHandler::sendError($timeSlotService, $calendarEvent, $timeZone);
@@ -359,9 +394,9 @@
359 394 'payment_status' => $booking->payment_status,
360 395 'booking_title' => $booking->getBookingTitle(true),
361 396 'author_name' => $booking->getHostDetails(false)['name'],
362 397 'booking_date' => DateTimeHelper::formatToLocale($booking->getAttendeeStartTime(), 'date'),
363 - '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'),
364 399 ];
365 400 }
366 401
367 402 return [
@@ -366,8 +401,89 @@
366 401
367 402 return [
368 403 'bookings' => $formattedBookings,
369 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')
370 486 ];
371 487 }
372 488
373 489 private static function sanitize_mapped_data($settings)