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/Hooks/Handlers/FrontEndHandler.php +86 -212 2.1.1 → 2.5.0 View file →
@@ -8,8 +8,9 @@
8 8 use FluentBooking\App\Models\CalendarSlot;
9 9 use FluentBooking\App\Services\BookingFieldService;
10 10 use FluentBooking\App\Services\BookingService;
11 11 use FluentBooking\App\Services\DateTimeHelper;
12 +use FluentBooking\App\Services\PublicTransStrings;
12 13 use FluentBooking\App\Services\Helper;
13 14 use FluentBooking\App\Services\LandingPage\LandingPageHandler;
14 15 use FluentBooking\App\Services\LandingPage\LandingPageHelper;
15 16 use FluentBooking\App\Hooks\Handlers\TimeSlotServiceHandler;
@@ -14,12 +15,12 @@
14 15 use FluentBooking\App\Services\LandingPage\LandingPageHelper;
15 16 use FluentBooking\App\Hooks\Handlers\TimeSlotServiceHandler;
16 17 use FluentBooking\App\Services\CalendarEventService;
17 18 use FluentBooking\App\Services\LocationService;
18 -use FluentBooking\App\Services\PermissionManager;
19 +use FluentBooking\App\Services\RescheduleService;
19 20 use FluentBooking\App\Services\CurrenciesHelper;
20 -use FluentBooking\App\Services\SanitizeService;
21 21 use FluentBooking\Framework\Support\Arr;
22 +use FluentBooking\App\Vite;
22 23
23 24 class FrontEndHandler
24 25 {
25 26 public function register()
@@ -71,19 +72,17 @@
71 72 if (!$calendar) {
72 73 return __('Calendar not found', 'fluent-booking');
73 74 }
74 75
75 - $assetUrl = App::getInstance('url.assets');
76 -
77 76 $localizeData = $this->getCalendarEventVars($calendar, $calendarEvent);
78 77 $localizeData['disable_author'] = $atts['disable_author'] == 'yes';
79 78 $localizeData['theme'] = $atts['theme'];
80 79
81 80 if (BookingFieldService::hasPhoneNumberField($localizeData['form_fields'])) {
82 - wp_enqueue_script('fluent-booking-phone-field', $assetUrl . 'public/js/phone-field.js', [], FLUENT_BOOKING_ASSETS_VERSION, true);
81 + Vite::enqueueScript('fluent-booking-phone-field', 'phone_field', [], FLUENT_BOOKING_ASSETS_VERSION);
83 82 }
84 83
85 - wp_enqueue_script('fluent-booking-public', $assetUrl . 'public/js/app.js', [], FLUENT_BOOKING_ASSETS_VERSION, true);
84 + Vite::enqueueScript('fluent-booking-public', 'public_app', [], FLUENT_BOOKING_ASSETS_VERSION);
86 85
87 86 $this->loadGlobalVars();
88 87 wp_localize_script(
89 88 'fluent-booking-public',
@@ -131,9 +130,9 @@
131 130 $event = CalendarEventService::processEvent($event);
132 131 $calendarEvents[$event->calendar_id][] = $event;
133 132 }
134 133
135 - $calendars = Calendar::query()->whereIn('id', $calendarIds)->get();
134 + $calendars = Calendar::query()->with('metas')->whereIn('id', $calendarIds)->get();
136 135
137 136 foreach ($calendars as $calendar) {
138 137 $calendar->activeEvents = $calendarEvents[$calendar->id] ?? [];
139 138 $eventOrder = $calendar->getMeta('event_order');
@@ -158,9 +157,9 @@
158 157
159 158 public function renderTeamHosts($calendars, $headerConfig = [])
160 159 {
161 160 $wrapperId = 'fcal_team_' . Helper::getNextIndex();
162 - wp_enqueue_script('fluent-booking-team', App::getInstance('url.assets') . 'public/js/team_app.js', [], FLUENT_BOOKING_ASSETS_VERSION, true);
161 + Vite::enqueueScript('fluent-booking-team', 'team_app', [], FLUENT_BOOKING_ASSETS_VERSION);
163 162
164 163 $vars = [];
165 164 foreach ($calendars as $calendar) {
166 165 $hostHtml = (string)(string)\FluentBooking\App\App::getInstance('view')->make('landing.author_html', [
@@ -190,10 +189,9 @@
190 189 }
191 190
192 191 wp_localize_script('fluent-booking-team', $wrapperId, $vars);
193 192
194 - $assetUrl = App::getInstance('url.assets');
195 - wp_enqueue_script('fluent-booking-public', $assetUrl . 'public/js/app.js', [], FLUENT_BOOKING_ASSETS_VERSION, true);
193 + Vite::enqueueScript('fluent-booking-public', 'public_app', [], FLUENT_BOOKING_ASSETS_VERSION);
196 194 $this->loadGlobalVars();
197 195
198 196 return App::make('view')->make('public.team_page', [
199 197 'hosts' => $calendars,
@@ -274,9 +272,9 @@
274 272
275 273 public function renderCalendarBlock($calendar, $headerConfig = [])
276 274 {
277 275 $wrapperId = 'fcal_calendar_' . Helper::getNextIndex();
278 - wp_enqueue_script('fluent-booking-calendar', App::getInstance('url.assets') . 'public/js/calendar_app.js', [], FLUENT_BOOKING_ASSETS_VERSION, true);
276 + Vite::enqueueScript('fluent-booking-calendar', 'calendar_app', [], FLUENT_BOOKING_ASSETS_VERSION);
279 277
280 278 $calendarHtml = (string)(string)\FluentBooking\App\App::getInstance('view')->make('landing.author_html', [
281 279 'author' => $calendar->getAuthorProfile(),
282 280 'calendar' => $calendar,
@@ -303,10 +301,9 @@
303 301 }
304 302
305 303 wp_localize_script('fluent-booking-calendar', $wrapperId, $vars);
306 304
307 - $assetUrl = App::getInstance('url.assets');
308 - wp_enqueue_script('fluent-booking-public', $assetUrl . 'public/js/app.js', [], FLUENT_BOOKING_ASSETS_VERSION, true);
305 + Vite::enqueueScript('fluent-booking-public', 'public_app', [], FLUENT_BOOKING_ASSETS_VERSION);
309 306 $this->loadGlobalVars();
310 307
311 308 return App::make('view')->make('public.calendar_page', [
312 309 'calendar' => $calendar,
@@ -329,9 +326,9 @@
329 326 'calendar_ids' => 'all',
330 327 'no_bookings' => __('No bookings found', 'fluent-booking'),
331 328 'per_page' => 10
332 329 ], $atts);
333 -
330 +
334 331 $atts['title'] = sanitize_text_field($atts['title']);
335 332 $atts['filter'] = sanitize_text_field($atts['filter']);
336 333 $atts['pagination'] = sanitize_text_field($atts['pagination']);
337 334 $atts['no_bookings'] = sanitize_text_field($atts['no_bookings']);
@@ -336,15 +333,15 @@
336 333 $atts['pagination'] = sanitize_text_field($atts['pagination']);
337 334 $atts['no_bookings'] = sanitize_text_field($atts['no_bookings']);
338 335
339 336 $userData = get_userdata(get_current_user_id());
340 -
337 +
341 338 $userEmail = $userData ? $userData->user_email : null;
342 -
339 +
343 340 if (!$userEmail) {
344 341 return __('Please login to view your bookings', 'fluent-booking');
345 342 }
346 -
343 +
347 344 $data = $_REQUEST; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
348 345
349 346 $perPage = intval(Arr::get($data, 'booking_per_page', $atts['per_page']));
350 347 $currentPage = intval(Arr::get($data, 'booking_page', 1));
@@ -390,9 +387,9 @@
390 387 $periodOptions = Helper::getBookingPeriodOptions();
391 388
392 389 $pageOptions = apply_filters('fluent_booking/booking_per_page_options', [5, 10, 15, 20, 50, 100]);
393 390
394 - wp_enqueue_script('fluent-booking-list', App::getInstance('url.assets') . 'public/js/bookings.js', [], FLUENT_BOOKING_ASSETS_VERSION, true);
391 + Vite::enqueueScript('fluent-booking-list', 'bookings', [], FLUENT_BOOKING_ASSETS_VERSION);
395 392
396 393 return App::make('view')->make('public.bookings', [
397 394 'bookings' => $bookings,
398 395 'attributes' => $atts,
@@ -421,9 +418,9 @@
421 418 if (empty($data['rescheduling_hash'])) {
422 419 return;
423 420 }
424 421
425 - add_filter('fluent_booking/schedule_custom_field_data', function ($array) {
422 + add_filter('fluent_booking/schedule_custom_field_data', function ($data) {
426 423 return [];
427 424 });
428 425
429 426 add_filter('fluent_booking/schedule_validation_rules_data', function ($data, $postedData, $calendarEvent)
@@ -451,77 +448,28 @@
451 448 'message' => __('Invalid rescheduling request', 'fluent-booking')
452 449 ], 422);
453 450 }
454 451
455 - $rescheduleBy = 'guest';
456 - $hostIds = $existingBooking->getHostIds();
457 - if (in_array(get_current_user_id(), $hostIds) || PermissionManager::userCan(['manage_all_data', 'manage_all_bookings'])) {
458 - $rescheduleBy = 'host';
459 - }
452 + $result = RescheduleService::reschedule(
453 + $existingBooking,
454 + $calendarEvent,
455 + $bookingData['start_time'],
456 + $bookingData['person_time_zone'],
457 + [
458 + 'reason' => Arr::get($postedData, 'rescheduling_reason', ''),
459 + 'host_user_id' => Arr::get($bookingData, 'host_user_id'),
460 + 'source' => __('Web UI', 'fluent-booking')
461 + ]
462 + );
460 463
461 - $existingBooking->updateMeta('rescheduled_by_type', $rescheduleBy);
462 -
463 - if ($rescheduleBy == 'guest' && !$existingBooking->canReschedule()) {
464 + if (is_wp_error($result)) {
464 465 wp_send_json([
465 - 'message' => $existingBooking->getRescheduleMessage()
466 + 'message' => $result->get_error_message()
466 467 ], 422);
467 468 }
468 469
469 - if ($bookingData['start_time'] == $existingBooking->start_time) {
470 - wp_send_json([
471 - 'message' => __('Sorry! you can not reschedule to the same time.', 'fluent-booking')
472 - ], 422);
473 - }
470 + $existingBooking = $result;
474 471
475 - $endDateTime = gmdate('Y-m-d H:i:s', strtotime($bookingData['start_time']) + ($existingBooking->slot_minutes * 60)); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
476 -
477 - $previousBooking = clone $existingBooking;
478 -
479 - if ($existingBooking->isMultiGuestBooking()) {
480 - // Need to handle group booking type here
481 - // check for existing group
482 - $parent = Booking::where('status', 'scheduled')
483 - ->where('event_id', $existingBooking->event_id)
484 - ->where('start_time', $bookingData['start_time'])
485 - ->orderBy('id', 'ASC')
486 - ->first();
487 -
488 - if ($parent) {
489 - $existingBooking->group_id = $parent->group_id;
490 - } else {
491 - $existingBooking->group_id = Helper::getNextBookingGroup();
492 - }
493 - }
494 -
495 - if ($existingBooking->isRoundRobinBooking()) {
496 - $hostId = $bookingData['host_user_id'];
497 - $existingBooking->host_user_id = $hostId;
498 - $existingBooking->hosts()->sync([$hostId]);
499 - }
500 -
501 - $existingBooking->start_time = $bookingData['start_time'];
502 - $existingBooking->person_time_zone = $bookingData['person_time_zone'];
503 - $existingBooking->end_time = $endDateTime;
504 - $existingBooking->save();
505 -
506 - $existingBooking->updateMeta('previous_meeting_time', $previousBooking->start_time);
507 -
508 - $reschedulingMessage = sanitize_textarea_field(Arr::get($postedData, 'rescheduling_reason'));
509 - if ($reschedulingMessage) {
510 - $existingBooking->updateMeta('reschedule_reason', $reschedulingMessage);
511 - }
512 -
513 - do_action('fluent_booking/log_booking_activity', [
514 - 'booking_id' => $existingBooking->id,
515 - 'type' => 'info',
516 - 'status' => 'closed',
517 - 'title' => __('Meeting Rescheduled', 'fluent-booking'),
518 - /* translators: %1$s is the user who rescheduled the meeting, %2$s is the previous date and time in UTC. */
519 - 'description' => sprintf(__('Meeting has been rescheduled by %1$s from Web UI. Previous date time: %2$s (UTC)', 'fluent-booking'), $rescheduleBy, $previousBooking->start_time)
520 - ]);
521 -
522 - do_action('fluent_booking/after_booking_rescheduled', $existingBooking, $previousBooking, $calendarEvent);
523 -
524 472 add_filter('fluent_booking/schedule_receipt_data', function ($data) {
525 473 $data['title'] = __('Your meeting has been rescheduled', 'fluent-booking');
526 474 return $data;
527 475 });
@@ -607,84 +555,12 @@
607 555 'ajaxurl' => admin_url('admin-ajax.php'),
608 556 'timezones' => DateTimeHelper::getFlatGroupedTimeZones(),
609 557 'current_person' => $currentPerson,
610 558 'start_day' => $startDay,
611 - 'i18' => [
612 - 'Timezone' => __('Timezone', 'fluent-booking'),
613 - 'Day' => __('Day', 'fluent-booking'),
614 - 'Days' => __('Days', 'fluent-booking'),
615 - 'Hour' => __('Hour', 'fluent-booking'),
616 - 'Hours' => __('Hours', 'fluent-booking'),
617 - 'Minute' => __('Minute', 'fluent-booking'),
618 - 'Minutes' => __('Minutes', 'fluent-booking'),
619 - 'week' => __('week', 'fluent-booking'),
620 - 'month' => __('month', 'fluent-booking'),
621 - 'year' => __('year', 'fluent-booking'),
622 - 'weeks' => __('weeks', 'fluent-booking'),
623 - 'months' => __('months', 'fluent-booking'),
624 - 'years' => __('years', 'fluent-booking'),
625 - 'Every' => __('Every', 'fluent-booking'),
626 - 'for' => __('for', 'fluent-booking'),
627 - 'Number of Occurrences' => __('Number of Occurrences', 'fluent-booking'),
628 - 'occurrence' => __('occurrence', 'fluent-booking'),
629 - 'occurrences' => __('occurrences', 'fluent-booking'),
630 - 'You can only book up to' => __('You can only book up to', 'fluent-booking'),
631 - 'at a time' => __('at a time', 'fluent-booking'),
632 - 'Enter Details' => __('Enter Details', 'fluent-booking'),
633 - 'Summary' => __('Summary', 'fluent-booking'),
634 - 'Payment Details' => __('Payment Details', 'fluent-booking'),
635 - 'Item' => __('Item', 'fluent-booking'),
636 - 'Price' => __('Price', 'fluent-booking'),
637 - 'Quantity' => __('Quantity', 'fluent-booking'),
638 - 'Subtotal:' => __('Subtotal:', 'fluent-booking'),
639 - 'Total:' => __('Total:', 'fluent-booking'),
640 - 'Total Payment' => __('Total Payment', 'fluent-booking'),
641 - 'Payment Method' => __('Payment Method', 'fluent-booking'),
642 - 'Pay Now' => __('Pay Now', 'fluent-booking'),
643 - 'processing' => __('Processing', 'fluent-booking'),
644 - 'date_time_config' => DateTimeHelper::getI18nDateTimeConfig(),
645 - 'Country' => __('Country', 'fluent-booking'),
646 - '12h' => _x('12h', 'date time format switch', 'fluent-booking'),
647 - '24h' => _x('24h', 'date time format switch', 'fluent-booking'),
648 - 'spots left' => _x('spots left', 'for how many spots left for available booking', 'fluent-booking'),
649 - 'spots remaining' => _x('spots remaining', 'for how many spots remaining for available booking', 'fluent-booking'),
650 - 'Next' => _x('Next', 'Booking form spot selection', 'fluent-booking'),
651 - 'Select on the Next Step' => __('Select on the Next Step', 'fluent-booking'),
652 - 'location options' => __('location options', 'fluent-booking'),
653 - 'Your address' => __('Your address', 'fluent-booking'),
654 - 'Organizer Phone Number' => __('Organizer Phone Number', 'fluent-booking'),
655 - 'In Person (Attendee Address)' => __('In Person (Attendee Address)', 'fluent-booking'),
656 - 'In Person (Organizer Address)' => __('In Person (Organizer Address)', 'fluent-booking'),
657 - 'Attendee Phone Number' => __('Attendee Phone Number', 'fluent-booking'),
658 - 'Google Meet' => __('Google Meet', 'fluent-booking'),
659 - 'Zoom Meeting' => __('Zoom Meeting', 'fluent-booking'),
660 - 'Online Meeting' => __('Online Meeting', 'fluent-booking'),
661 - 'Phone Call' => __('Phone Call', 'fluent-booking'),
662 - 'Processing...' => __('Processing...', 'fluent-booking'),
663 - 'Loading Payment Processor...' => __('Loading Payment Processor...', 'fluent-booking'),
664 - 'PM' => __('PM', 'fluent-booking'),
665 - 'AM' => __('AM', 'fluent-booking'),
666 - 'Name' => __('Name', 'fluent-booking'),
667 - 'Email' => __('Email', 'fluent-booking'),
668 - 'Date' => __('Date', 'fluent-booking'),
669 - 'Time' => __('Time', 'fluent-booking'),
670 - 'per occurrence' => __('per occurrence', 'fluent-booking'),
671 - 'per guest' => __('per guest', 'fluent-booking'),
672 - 'Add guest' => __('Add guest', 'fluent-booking'),
673 - 'Add guests' => __('Add guests', 'fluent-booking'),
674 - 'Add another' => __('Add another', 'fluent-booking'),
675 - 'Choose File' => __('Choose File', 'fluent-booking'),
676 - 'This field is required.' => __('This field is required.', 'fluent-booking'),
677 - 'No availability in' => __('No availability in', 'fluent-booking'),
678 - 'View next month' => __('View next month', 'fluent-booking'),
679 - 'View previous month' => __('View previous month', 'fluent-booking'),
680 - 'No_payment_method_description' => __('No activated payment method found. If you are an admin please check the event payment settings', 'fluent-booking'),
681 - 'Please fill up the required data' => __('Please fill up the required data', 'fluent-booking'),
682 - 'Please select a valid payment method' => __('Please select a valid payment method', 'fluent-booking'),
683 - 'Please Select' => __('Please Select', 'fluent-booking'),
684 - 'Something is wrong!' => __('Something is wrong!', 'fluent-booking'),
685 - 'Requires Confirmation' => __('Requires Confirmation', 'fluent-booking'),
686 - ],
559 + // Generated from the i18() calls in resources/public; see scripts/i18n.js.
560 + 'i18' => array_merge(PublicTransStrings::getStrings(), [
561 + 'date_time_config' => DateTimeHelper::getI18nDateTimeConfig(),
562 + ]),
687 563 'theme' => Arr::get(get_option('_fluent_booking_settings'), 'theme', 'system-default'),
688 564 'currency_settings' => CurrenciesHelper::getGlobalCurrencySettings()
689 565 ];
690 566
@@ -741,9 +617,9 @@
741 617 'start_date.required' => __('Please select a date and time', 'fluent-booking')
742 618 ];
743 619
744 620 if ($calendarEvent->isPhoneRequired()) {
745 - $rules['phone_number'] = 'required';
621 + $rules['phone_number'] = ['required', $this->validPhoneNumberRule()];
746 622 $messages['phone_number.required'] = __('Please provide your phone number', 'fluent-booking');
747 623 } else if ($calendarEvent->isAddressRequired()) {
748 624 $rules['address'] = 'required';
749 625 $messages['address.required'] = __('Please provide your Address', 'fluent-booking');
@@ -754,12 +630,13 @@
754 630 $selectedLocation = LocationService::getLocationDetails($calendarEvent, Arr::get($postedData, 'location_config', []), $postedData);
755 631 $selectedLocationDriver = Arr::get($selectedLocation, 'type');
756 632 // is user input required
757 633 if (in_array($selectedLocationDriver, ['in_person_guest', 'phone_guest'])) {
758 - $rules['location_config.user_location_input'] = 'required';
759 634 if ($selectedLocationDriver == 'in_person_guest') {
635 + $rules['location_config.user_location_input'] = 'required';
760 636 $messages['location_config.user_location_input.required'] = __('Please provide your address', 'fluent-booking');
761 637 } else {
638 + $rules['location_config.user_location_input'] = ['required', $this->validPhoneNumberRule()];
762 639 $messages['location_config.user_location_input.required'] = __('Please provide your phone number', 'fluent-booking');
763 640 }
764 641 }
765 642 }
@@ -804,9 +681,9 @@
804 681 ], 422);
805 682 }
806 683
807 684 $customFieldsData = BookingFieldService::getCustomFieldsData($postedData, $calendarEvent);
808 - $customFieldsData = apply_filters('fluent_booking/schedule_custom_field_data', $customFieldsData, $customFieldsData, $calendarEvent);
685 + $customFieldsData = apply_filters('fluent_booking/schedule_custom_field_data', $customFieldsData, $calendarEvent);
809 686
810 687 if (is_wp_error($customFieldsData)) {
811 688 wp_send_json([
812 689 'message' => $customFieldsData->get_error_message(),
@@ -896,10 +773,12 @@
896 773 if (is_wp_error($timeSlotService)) {
897 774 return TimeSlotServiceHandler::sendError($timeSlotService, $calendarEvent, $timezone);
898 775 }
899 776
900 - $availableSpot = $timeSlotService->isSpotAvailable($bookingData['start_time'], $bookingData['end_time'], $duration);
777 + $isSlotLocked = Helper::lockRoundRobinSlot($calendarEvent, $bookingData['start_time'], $bookingData['end_time']);
901 778
779 + $availableSpot = $isSlotLocked ? $timeSlotService->isSpotAvailable($bookingData['start_time'], $bookingData['end_time'], $duration) : false;
780 +
902 781 if (!$availableSpot) {
903 782 wp_send_json([
904 783 'message' => __('This selected time slot is not available. Maybe someone booked the spot just a few seconds ago.', 'fluent-booking')
905 784 ], 422);
@@ -946,8 +825,20 @@
946 825 'booking_hash' => $booking->hash
947 826 ], $booking), 200);
948 827 }
949 828
829 + /**
830 + * @return \Closure
831 + */
832 + private function validPhoneNumberRule()
833 + {
834 + return function ($attribute, $value) {
835 + if (!empty($value) && !Helper::isValidPhoneNumber($value)) {
836 + return __('Please provide a valid phone number', 'fluent-booking');
837 + }
838 + };
839 + }
840 +
950 841 public function ajaxGetAvailableDates()
951 842 {
952 843 if (!Helper::checkRateLimit('available_dates', 30)) {
953 844 wp_send_json_error(['message' => __('Too many requests. Please try again in a minute.', 'fluent-booking')], 429);
@@ -956,20 +847,30 @@
956 847 $startBenchmark = microtime(true);
957 848
958 849 $request = $_REQUEST; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
959 850
960 - $eventId = (int)$request['event_id'];
851 + $eventId = (int)Arr::get($request, 'event_id');
961 852
962 - $rescheduling = Arr::get($request, 'rescheduling', 'no');
853 + $reschedulingHash = sanitize_text_field(Arr::get($request, 'rescheduling_hash', '')); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
963 854
964 - $calendarEvent = CalendarSlot::findOrfail($eventId);
855 + $calendarEvent = $eventId ? CalendarSlot::find($eventId) : null;
965 856
966 - if (!$calendarEvent || ($calendarEvent->status != 'active' && $rescheduling == 'no')) {
857 + if (!$calendarEvent) {
967 858 wp_send_json([
968 859 'message' => __('Sorry, the host is not accepting any new bookings at the moment.', 'fluent-booking')
969 860 ], 422);
970 861 }
971 862
863 + if ($calendarEvent->status != 'active') {
864 + $existingBooking = $reschedulingHash ? Booking::where('hash', $reschedulingHash)->first() : null;
865 +
866 + if (!$existingBooking || (int)$existingBooking->event_id !== (int)$calendarEvent->id) {
867 + wp_send_json([
868 + 'message' => __('Sorry, the host is not accepting any new bookings at the moment.', 'fluent-booking')
869 + ], 422);
870 + }
871 + }
872 +
972 873 $calendar = $calendarEvent->calendar;
973 874 $startDate = sanitize_text_field(Arr::get($request, 'start_date')); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
974 875
975 876 if (!$startDate) {
@@ -1012,8 +913,20 @@
1012 913 }
1013 914
1014 915 public function getCalendarEventVars(Calendar $calendar, CalendarSlot $calendarEvent)
1015 916 {
917 + static $globalConfig = null;
918 + if ($globalConfig === null) {
919 + $globalConfig = [
920 + 'time_format' => Arr::get(get_option('_fluent_booking_settings'), 'time_format', '12'),
921 + 'date_formatter' => DateTimeHelper::getDateFormatter(true),
922 + 'isRtl' => Helper::fluentbooking_is_rtl(),
923 + 'has_pro' => defined('FLUENT_BOOKING_PRO_DIR_FILE'),
924 + 'duration_lookup' => Helper::getDurationLookup(),
925 + 'multi_duration_lookup' => Helper::getDurationLookup(true),
926 + ];
927 + }
928 +
1016 929 $calendarEvent->description = wpautop($calendarEvent->description);
1017 930 $calendarEvent->location_icon_html = $calendarEvent->defaultLocationHtml();
1018 931 $formFields = BookingFieldService::getBookingFields($calendarEvent);
1019 932
@@ -1024,9 +937,9 @@
1024 937 'min_bookable_date' => $calendarEvent->getMinBookableDateTime(),
1025 938 'is_display_spots' => $calendarEvent->isDisplaySpots(),
1026 939 'duration' => $calendarEvent->getDefaultDuration(),
1027 940 'title' => $calendarEvent->title,
1028 - 'location_settings' => $this->sanitizePublicLocationSettings($calendarEvent->location_settings),
941 + 'location_settings' => LocationService::sanitizePublicLocationSettings($calendarEvent->location_settings),
1029 942 'location_icon_html' => $calendarEvent->location_icon_html,
1030 943 'description' => $calendarEvent->description,
1031 944 'pre_selects' => null,
1032 945 'settings' => $this->sanitizePublicEventSettings($calendarEvent->settings),
@@ -1031,9 +944,9 @@
1031 944 'pre_selects' => null,
1032 945 'settings' => $this->sanitizePublicEventSettings($calendarEvent->settings),
1033 946 'type' => $calendarEvent->type,
1034 947 'event_type' => $calendarEvent->event_type,
1035 - 'time_format' => Arr::get(get_option('_fluent_booking_settings'), 'time_format', '12'),
948 + 'time_format' => $globalConfig['time_format'],
1036 949 ];
1037 950
1038 951 $author = $calendar->getAuthorProfile(true);
1039 952 $author['name'] = $calendar->title;
@@ -1046,13 +959,13 @@
1046 959 'Schedule_Meeting' => __('Schedule Meeting', 'fluent-booking'),
1047 960 'Continue_to_Payments' => __('Continue to Payments', 'fluent-booking'),
1048 961 'Confirm_Payment' => __('Confirm Payment', 'fluent-booking'),
1049 962 ],
1050 - 'date_formatter' => DateTimeHelper::getDateFormatter(true),
1051 - 'isRtl' => Helper::fluentbooking_is_rtl(),
1052 - 'has_pro' => defined('FLUENT_BOOKING_PRO_DIR_FILE'),
1053 - 'duration_lookup' => Helper::getDurationLookup(),
1054 - 'multi_duration_lookup' => Helper::getDurationLookup(true)
963 + 'date_formatter' => $globalConfig['date_formatter'],
964 + 'isRtl' => $globalConfig['isRtl'],
965 + 'has_pro' => $globalConfig['has_pro'],
966 + 'duration_lookup' => $globalConfig['duration_lookup'],
967 + 'multi_duration_lookup' => $globalConfig['multi_duration_lookup']
1055 968 ];
1056 969
1057 970 $eventVars['form_fields'] = array_values($eventVars['form_fields']);
1058 971
@@ -1060,47 +973,8 @@
1060 973 $eventVars['team_member_profiles'] = $calendarEvent->getAuthorProfiles(true);
1061 974 }
1062 975
1063 976 return apply_filters('fluent_booking/public_event_vars', $eventVars, $calendarEvent);
1064 - }
1065 -
1066 - private function sanitizePublicLocationSettings($locationSettings)
1067 - {
1068 - if (!is_array($locationSettings)) {
1069 - return [];
1070 - }
1071 -
1072 - $safe = [];
1073 - foreach ($locationSettings as $location) {
1074 - if (!is_array($location)) {
1075 - continue;
1076 - }
1077 -
1078 - $type = Arr::get($location, 'type');
1079 - $displayOnBooking = Arr::get($location, 'display_on_booking') === 'yes';
1080 -
1081 - $sanitized = [
1082 - 'type' => $type,
1083 - 'title' => Arr::get($location, 'title'),
1084 - 'display_on_booking' => Arr::get($location, 'display_on_booking', 'no'),
1085 - ];
1086 -
1087 - // Only expose host-private fields when the host explicitly opted
1088 - // in to display them before booking.
1089 - if ($displayOnBooking) {
1090 - if ($type === 'online_meeting') {
1091 - $sanitized['meeting_link'] = Arr::get($location, 'meeting_link');
1092 - } elseif ($type === 'phone_organizer') {
1093 - $sanitized['host_phone_number'] = Arr::get($location, 'host_phone_number');
1094 - } elseif (in_array($type, ['in_person_organizer', 'custom'], true)) {
1095 - $sanitized['description'] = Arr::get($location, 'description');
1096 - }
1097 - }
1098 -
1099 - $safe[] = $sanitized;
1100 - }
1101 -
1102 - return $safe;
1103 977 }
1104 978
1105 979 private function sanitizePublicEventSettings($settings)
1106 980 {