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 +169 -185 1.10.0 → 2.5.0 View file →
@@ -8,17 +8,19 @@
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;
15 +use FluentBooking\App\Services\LandingPage\LandingPageHelper;
14 16 use FluentBooking\App\Hooks\Handlers\TimeSlotServiceHandler;
15 17 use FluentBooking\App\Services\CalendarEventService;
16 18 use FluentBooking\App\Services\LocationService;
17 -use FluentBooking\App\Services\PermissionManager;
19 +use FluentBooking\App\Services\RescheduleService;
18 20 use FluentBooking\App\Services\CurrenciesHelper;
19 -use FluentBooking\App\Services\SanitizeService;
20 21 use FluentBooking\Framework\Support\Arr;
22 +use FluentBooking\App\Vite;
21 23
22 24 class FrontEndHandler
23 25 {
24 26 public function register()
@@ -70,19 +72,17 @@
70 72 if (!$calendar) {
71 73 return __('Calendar not found', 'fluent-booking');
72 74 }
73 75
74 - $assetUrl = App::getInstance('url.assets');
75 -
76 76 $localizeData = $this->getCalendarEventVars($calendar, $calendarEvent);
77 77 $localizeData['disable_author'] = $atts['disable_author'] == 'yes';
78 78 $localizeData['theme'] = $atts['theme'];
79 79
80 80 if (BookingFieldService::hasPhoneNumberField($localizeData['form_fields'])) {
81 - 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);
82 82 }
83 83
84 - 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);
85 85
86 86 $this->loadGlobalVars();
87 87 wp_localize_script(
88 88 'fluent-booking-public',
@@ -130,9 +130,9 @@
130 130 $event = CalendarEventService::processEvent($event);
131 131 $calendarEvents[$event->calendar_id][] = $event;
132 132 }
133 133
134 - $calendars = Calendar::query()->whereIn('id', $calendarIds)->get();
134 + $calendars = Calendar::query()->with('metas')->whereIn('id', $calendarIds)->get();
135 135
136 136 foreach ($calendars as $calendar) {
137 137 $calendar->activeEvents = $calendarEvents[$calendar->id] ?? [];
138 138 $eventOrder = $calendar->getMeta('event_order');
@@ -157,9 +157,9 @@
157 157
158 158 public function renderTeamHosts($calendars, $headerConfig = [])
159 159 {
160 160 $wrapperId = 'fcal_team_' . Helper::getNextIndex();
161 - 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);
162 162
163 163 $vars = [];
164 164 foreach ($calendars as $calendar) {
165 165 $hostHtml = (string)(string)\FluentBooking\App\App::getInstance('view')->make('landing.author_html', [
@@ -189,10 +189,9 @@
189 189 }
190 190
191 191 wp_localize_script('fluent-booking-team', $wrapperId, $vars);
192 192
193 - $assetUrl = App::getInstance('url.assets');
194 - 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);
195 194 $this->loadGlobalVars();
196 195
197 196 return App::make('view')->make('public.team_page', [
198 197 'hosts' => $calendars,
@@ -230,15 +229,29 @@
230 229 if (!$calendar) {
231 230 return '';
232 231 }
233 232
233 + $settings = LandingPageHelper::getSettings($calendar, 'public');
234 +
234 235 $calendarEventQuery = CalendarSlot::where('calendar_id', $calendar->id)
235 236 ->where('status', 'active');
236 -
237 +
238 + $enabledEvents = [];
239 + $isEnabledOnly = false;
240 + if ($settings['show_type'] != 'all') {
241 + $isEnabledOnly = true;
242 + $enabledEvents = $settings['enabled_slots'];
243 + }
244 +
237 245 if ($eventIds && $eventIds != 'all') {
238 - $calendarEventQuery->whereIn('id', $eventIds);
246 + $isEnabledOnly = true;
247 + $enabledEvents = !empty($enabledEvents) ? array_intersect($enabledEvents, $eventIds) : $eventIds;
239 248 }
240 249
250 + if (!empty($enabledEvents) || $isEnabledOnly) {
251 + $calendarEventQuery->whereIn('id', $enabledEvents);
252 + }
253 +
241 254 $calendarEvents = $calendarEventQuery->get();
242 255
243 256 if ($calendarEvents->isEmpty()) {
244 257 return '';
@@ -259,9 +272,9 @@
259 272
260 273 public function renderCalendarBlock($calendar, $headerConfig = [])
261 274 {
262 275 $wrapperId = 'fcal_calendar_' . Helper::getNextIndex();
263 - 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);
264 277
265 278 $calendarHtml = (string)(string)\FluentBooking\App\App::getInstance('view')->make('landing.author_html', [
266 279 'author' => $calendar->getAuthorProfile(),
267 280 'calendar' => $calendar,
@@ -288,10 +301,9 @@
288 301 }
289 302
290 303 wp_localize_script('fluent-booking-calendar', $wrapperId, $vars);
291 304
292 - $assetUrl = App::getInstance('url.assets');
293 - 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);
294 306 $this->loadGlobalVars();
295 307
296 308 return App::make('view')->make('public.calendar_page', [
297 309 'calendar' => $calendar,
@@ -314,9 +326,9 @@
314 326 'calendar_ids' => 'all',
315 327 'no_bookings' => __('No bookings found', 'fluent-booking'),
316 328 'per_page' => 10
317 329 ], $atts);
318 -
330 +
319 331 $atts['title'] = sanitize_text_field($atts['title']);
320 332 $atts['filter'] = sanitize_text_field($atts['filter']);
321 333 $atts['pagination'] = sanitize_text_field($atts['pagination']);
322 334 $atts['no_bookings'] = sanitize_text_field($atts['no_bookings']);
@@ -321,15 +333,15 @@
321 333 $atts['pagination'] = sanitize_text_field($atts['pagination']);
322 334 $atts['no_bookings'] = sanitize_text_field($atts['no_bookings']);
323 335
324 336 $userData = get_userdata(get_current_user_id());
325 -
337 +
326 338 $userEmail = $userData ? $userData->user_email : null;
327 -
339 +
328 340 if (!$userEmail) {
329 341 return __('Please login to view your bookings', 'fluent-booking');
330 342 }
331 -
343 +
332 344 $data = $_REQUEST; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
333 345
334 346 $perPage = intval(Arr::get($data, 'booking_per_page', $atts['per_page']));
335 347 $currentPage = intval(Arr::get($data, 'booking_page', 1));
@@ -375,9 +387,9 @@
375 387 $periodOptions = Helper::getBookingPeriodOptions();
376 388
377 389 $pageOptions = apply_filters('fluent_booking/booking_per_page_options', [5, 10, 15, 20, 50, 100]);
378 390
379 - 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);
380 392
381 393 return App::make('view')->make('public.bookings', [
382 394 'bookings' => $bookings,
383 395 'attributes' => $atts,
@@ -406,9 +418,9 @@
406 418 if (empty($data['rescheduling_hash'])) {
407 419 return;
408 420 }
409 421
410 - add_filter('fluent_booking/schedule_custom_field_data', function ($array) {
422 + add_filter('fluent_booking/schedule_custom_field_data', function ($data) {
411 423 return [];
412 424 });
413 425
414 426 add_filter('fluent_booking/schedule_validation_rules_data', function ($data, $postedData, $calendarEvent)
@@ -436,77 +448,28 @@
436 448 'message' => __('Invalid rescheduling request', 'fluent-booking')
437 449 ], 422);
438 450 }
439 451
440 - $rescheduleBy = 'guest';
441 - $hostIds = $existingBooking->getHostIds();
442 - if (in_array(get_current_user_id(), $hostIds) || PermissionManager::userCan('manage_all_bookings')) {
443 - $rescheduleBy = 'host';
444 - }
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 + );
445 463
446 - $existingBooking->updateMeta('rescheduled_by_type', $rescheduleBy);
447 -
448 - if ($rescheduleBy == 'guest' && !$existingBooking->canReschedule()) {
464 + if (is_wp_error($result)) {
449 465 wp_send_json([
450 - 'message' => $existingBooking->getRescheduleMessage()
466 + 'message' => $result->get_error_message()
451 467 ], 422);
452 468 }
453 469
454 - if ($bookingData['start_time'] == $existingBooking->start_time) {
455 - wp_send_json([
456 - 'message' => __('Sorry! you can not reschedule to the same time.', 'fluent-booking')
457 - ], 422);
458 - }
470 + $existingBooking = $result;
459 471
460 - $endDateTime = gmdate('Y-m-d H:i:s', strtotime($bookingData['start_time']) + ($existingBooking->slot_minutes * 60)); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
461 -
462 - $previousBooking = clone $existingBooking;
463 -
464 - if ($existingBooking->isMultiGuestBooking()) {
465 - // Need to handle group booking type here
466 - // check for existing group
467 - $parent = Booking::where('status', 'scheduled')
468 - ->where('event_id', $existingBooking->event_id)
469 - ->where('start_time', $bookingData['start_time'])
470 - ->orderBy('id', 'ASC')
471 - ->first();
472 -
473 - if ($parent) {
474 - $existingBooking->group_id = $parent->group_id;
475 - } else {
476 - $existingBooking->group_id = Helper::getNextBookingGroup();
477 - }
478 - }
479 -
480 - if ($existingBooking->isRoundRobinBooking()) {
481 - $hostId = $bookingData['host_user_id'];
482 - $existingBooking->host_user_id = $hostId;
483 - $existingBooking->hosts()->sync([$hostId]);
484 - }
485 -
486 - $existingBooking->start_time = $bookingData['start_time'];
487 - $existingBooking->person_time_zone = $bookingData['person_time_zone'];
488 - $existingBooking->end_time = $endDateTime;
489 - $existingBooking->save();
490 -
491 - $existingBooking->updateMeta('previous_meeting_time', $previousBooking->start_time);
492 -
493 - $reschedulingMessage = sanitize_textarea_field(Arr::get($postedData, 'rescheduling_reason'));
494 - if ($reschedulingMessage) {
495 - $existingBooking->updateMeta('reschedule_reason', $reschedulingMessage);
496 - }
497 -
498 - do_action('fluent_booking/log_booking_activity', [
499 - 'booking_id' => $existingBooking->id,
500 - 'type' => 'info',
501 - 'status' => 'closed',
502 - 'title' => __('Meeting Rescheduled', 'fluent-booking'),
503 - /* translators: %1$s is the user who rescheduled the meeting, %2$s is the previous date and time in UTC. */
504 - 'description' => sprintf(__('Meeting has been rescheduled by %1$s from Web UI. Previous date time: %2$s (UTC)', 'fluent-booking'), $rescheduleBy, $previousBooking->start_time)
505 - ]);
506 -
507 - do_action('fluent_booking/after_booking_rescheduled', $existingBooking, $previousBooking, $calendarEvent);
508 -
509 472 add_filter('fluent_booking/schedule_receipt_data', function ($data) {
510 473 $data['title'] = __('Your meeting has been rescheduled', 'fluent-booking');
511 474 return $data;
512 475 });
@@ -592,70 +555,12 @@
592 555 'ajaxurl' => admin_url('admin-ajax.php'),
593 556 'timezones' => DateTimeHelper::getFlatGroupedTimeZones(),
594 557 'current_person' => $currentPerson,
595 558 'start_day' => $startDay,
596 - 'i18' => [
597 - 'Timezone' => __('Timezone', 'fluent-booking'),
598 - 'Day' => __('Day', 'fluent-booking'),
599 - 'Days' => __('Days', 'fluent-booking'),
600 - 'Hour' => __('Hour', 'fluent-booking'),
601 - 'Hours' => __('Hours', 'fluent-booking'),
602 - 'Minute' => __('Minute', 'fluent-booking'),
603 - 'Minutes' => __('Minutes', 'fluent-booking'),
604 - 'Enter Details' => __('Enter Details', 'fluent-booking'),
605 - 'Summary' => __('Summary', 'fluent-booking'),
606 - 'Payment Details' => __('Payment Details', 'fluent-booking'),
607 - 'Item' => __('Item', 'fluent-booking'),
608 - 'Price' => __('Price', 'fluent-booking'),
609 - 'Quantity' => __('Quantity', 'fluent-booking'),
610 - 'Subtotal:' => __('Subtotal:', 'fluent-booking'),
611 - 'Total:' => __('Total:', 'fluent-booking'),
612 - 'Total Payment' => __('Total Payment', 'fluent-booking'),
613 - 'Payment Method' => __('Payment Method', 'fluent-booking'),
614 - 'Pay Now' => __('Pay Now', 'fluent-booking'),
615 - 'processing' => __('Processing', 'fluent-booking'),
616 - 'date_time_config' => DateTimeHelper::getI18nDateTimeConfig(),
617 - 'Country' => __('Country', 'fluent-booking'),
618 - '12h' => _x('12h', 'date time format switch', 'fluent-booking'),
619 - '24h' => _x('24h', 'date time format switch', 'fluent-booking'),
620 - 'spots left' => _x('spots left', 'for how many spots left for available booking', 'fluent-booking'),
621 - 'spots remaining' => _x('spots remaining', 'for how many spots remaining for available booking', 'fluent-booking'),
622 - 'Next' => _x('Next', 'Booking form spot selection', 'fluent-booking'),
623 - 'Select on the Next Step' => __('Select on the Next Step', 'fluent-booking'),
624 - 'location options' => __('location options', 'fluent-booking'),
625 - 'Your address' => __('Your address', 'fluent-booking'),
626 - 'Organizer Phone Number' => __('Organizer Phone Number', 'fluent-booking'),
627 - 'In Person (Attendee Address)' => __('In Person (Attendee Address)', 'fluent-booking'),
628 - 'In Person (Organizer Address)' => __('In Person (Organizer Address)', 'fluent-booking'),
629 - 'Attendee Phone Number' => __('Attendee Phone Number', 'fluent-booking'),
630 - 'Google Meet' => __('Google Meet', 'fluent-booking'),
631 - 'Zoom Meeting' => __('Zoom Meeting', 'fluent-booking'),
632 - 'Online Meeting' => __('Online Meeting', 'fluent-booking'),
633 - 'Phone Call' => __('Phone Call', 'fluent-booking'),
634 - 'Processing...' => __('Processing...', 'fluent-booking'),
635 - 'Loading Payment Processor...' => __('Loading Payment Processor...', 'fluent-booking'),
636 - 'PM' => __('PM', 'fluent-booking'),
637 - 'AM' => __('AM', 'fluent-booking'),
638 - 'Name' => __('Name', 'fluent-booking'),
639 - 'Email' => __('Email', 'fluent-booking'),
640 - 'Date' => __('Date', 'fluent-booking'),
641 - 'Time' => __('Time', 'fluent-booking'),
642 - 'per guest' => __('per guest', 'fluent-booking'),
643 - 'Add guest' => __('Add guest', 'fluent-booking'),
644 - 'Add guests' => __('Add guests', 'fluent-booking'),
645 - 'Add another' => __('Add another', 'fluent-booking'),
646 - 'Choose File' => __('Choose File', 'fluent-booking'),
647 - 'This field is required.' => __('This field is required.', 'fluent-booking'),
648 - 'No availability in' => __('No availability in', 'fluent-booking'),
649 - 'View next month' => __('View next month', 'fluent-booking'),
650 - 'View previous month' => __('View previous month', 'fluent-booking'),
651 - 'No_payment_method_description' => __('No activated payment method found. If you are an admin please check the event payment settings', 'fluent-booking'),
652 - 'Please fill up the required data' => __('Please fill up the required data', 'fluent-booking'),
653 - 'Please select a valid payment method' => __('Please select a valid payment method', 'fluent-booking'),
654 - 'Please Select' => __('Please Select', 'fluent-booking'),
655 - 'Something is wrong!' => __('Something is wrong!', 'fluent-booking'),
656 - 'Requires Confirmation' => __('Requires Confirmation', 'fluent-booking'),
657 - ],
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 + ]),
658 563 'theme' => Arr::get(get_option('_fluent_booking_settings'), 'theme', 'system-default'),
659 564 'currency_settings' => CurrenciesHelper::getGlobalCurrencySettings()
660 565 ];
661 566
@@ -669,8 +574,12 @@
669 574 }
670 575
671 576 public function ajaxScheduleMeeting()
672 577 {
578 + if (!Helper::checkRateLimit('schedule_meeting', 15)) {
579 + wp_send_json_error(['message' => __('Too many requests. Please try again in a minute.', 'fluent-booking')], 429);
580 + }
581 +
673 582 $app = App::getInstance();
674 583
675 584 $postedData = $_REQUEST; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
676 585
@@ -688,12 +597,17 @@
688 597
689 598 do_action('fluent_booking/starting_scheduling_ajax', $postedData);
690 599
691 600 $rules = [
692 - 'name' => 'required',
693 - 'email' => 'required|email',
694 - 'timezone' => 'required',
695 - 'start_date' => 'required'
601 + 'name' => 'required',
602 + 'email' => 'required|email',
603 + 'timezone' => 'required',
604 + 'start_date' => 'required',
605 + 'utm_source' => 'max:192',
606 + 'utm_medium' => 'max:192',
607 + 'utm_campaign' => 'max:192',
608 + 'utm_term' => 'max:192',
609 + 'utm_content' => 'max:192',
696 610 ];
697 611
698 612 $messages = [
699 613 'name.required' => __('Please enter your name', 'fluent-booking'),
@@ -703,9 +617,9 @@
703 617 'start_date.required' => __('Please select a date and time', 'fluent-booking')
704 618 ];
705 619
706 620 if ($calendarEvent->isPhoneRequired()) {
707 - $rules['phone_number'] = 'required';
621 + $rules['phone_number'] = ['required', $this->validPhoneNumberRule()];
708 622 $messages['phone_number.required'] = __('Please provide your phone number', 'fluent-booking');
709 623 } else if ($calendarEvent->isAddressRequired()) {
710 624 $rules['address'] = 'required';
711 625 $messages['address.required'] = __('Please provide your Address', 'fluent-booking');
@@ -716,12 +630,13 @@
716 630 $selectedLocation = LocationService::getLocationDetails($calendarEvent, Arr::get($postedData, 'location_config', []), $postedData);
717 631 $selectedLocationDriver = Arr::get($selectedLocation, 'type');
718 632 // is user input required
719 633 if (in_array($selectedLocationDriver, ['in_person_guest', 'phone_guest'])) {
720 - $rules['location_config.user_location_input'] = 'required';
721 634 if ($selectedLocationDriver == 'in_person_guest') {
635 + $rules['location_config.user_location_input'] = 'required';
722 636 $messages['location_config.user_location_input.required'] = __('Please provide your address', 'fluent-booking');
723 637 } else {
638 + $rules['location_config.user_location_input'] = ['required', $this->validPhoneNumberRule()];
724 639 $messages['location_config.user_location_input.required'] = __('Please provide your phone number', 'fluent-booking');
725 640 }
726 641 }
727 642 }
@@ -727,13 +642,8 @@
727 642 }
728 643
729 644 $duration = (int)$calendarEvent->getDuration(Arr::get($postedData, 'duration', null));
730 645
731 - if ($calendarEvent->isPaymentEnabled($duration)) {
732 - $rules['payment_method'] = 'required';
733 - $messages['payment_method.required'] = __('Please select a valid payment method', 'fluent-booking');
734 - }
735 -
736 646 if ($additionalGuests = Arr::get($postedData, 'guests', [])) {
737 647 if ($calendarEvent->isMultiGuestEvent()) {
738 648 $additionalGuests = $this->sanitize_mapped_data($additionalGuests);
739 649 $additionalGuests = array_values(array_filter($additionalGuests, function ($guest) {
@@ -763,17 +673,17 @@
763 673 ], $postedData, $calendarEvent);
764 674
765 675 $validator = $app->validator->make($postedData, $validationConfig['rules'], $validationConfig['messages']);
766 676 if ($validator->validate()->fails()) {
677 + $errorMessage = $validator->firstError() ?: __('Please fill up the required data', 'fluent-booking');
767 678 wp_send_json([
768 - 'message' => __('Please fill up the required data', 'fluent-booking'),
679 + 'message' => $errorMessage,
769 680 'errors' => $validator->errors()
770 681 ], 422);
771 - return;
772 682 }
773 683
774 684 $customFieldsData = BookingFieldService::getCustomFieldsData($postedData, $calendarEvent);
775 - $customFieldsData = apply_filters('fluent_booking/schedule_custom_field_data', $customFieldsData, $customFieldsData, $calendarEvent);
685 + $customFieldsData = apply_filters('fluent_booking/schedule_custom_field_data', $customFieldsData, $calendarEvent);
776 686
777 687 if (is_wp_error($customFieldsData)) {
778 688 wp_send_json([
779 689 'message' => $customFieldsData->get_error_message(),
@@ -778,9 +688,8 @@
778 688 wp_send_json([
779 689 'message' => $customFieldsData->get_error_message(),
780 690 'errors' => $customFieldsData->get_error_data()
781 691 ], 422);
782 - return;
783 692 }
784 693
785 694 $validateDateFields = BookingFieldService::validateDateFields($customFieldsData, $calendarEvent);
786 695
@@ -787,9 +696,8 @@
787 696 if (is_wp_error($validateDateFields)) {
788 697 wp_send_json([
789 698 'message' => $validateDateFields->get_error_message(),
790 699 ], 422);
791 - return;
792 700 }
793 701
794 702 $startDate = Arr::get($postedData, 'start_date');
795 703 $timezone = sanitize_text_field(Arr::get($postedData, 'timezone', 'UTC'));
@@ -810,9 +718,9 @@
810 718 $startDateTime = DateTimeHelper::convertToUtc($startDate, $timezone);
811 719 $endDateTime = gmdate('Y-m-d H:i:s', strtotime($startDateTime) + ($duration * 60)); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
812 720 }
813 721
814 - $bookingData = [
722 + $bookingData = apply_filters('fluent_booking/initialize_booking_data', [
815 723 'person_time_zone' => sanitize_text_field($timezone),
816 724 'start_time' => $startDateTime,
817 725 'end_time' => $endDateTime,
818 726 'name' => sanitize_text_field($postedData['name']),
@@ -817,9 +725,9 @@
817 725 'end_time' => $endDateTime,
818 726 'name' => sanitize_text_field($postedData['name']),
819 727 'email' => sanitize_email($postedData['email']),
820 728 'message' => sanitize_textarea_field(wp_unslash(Arr::get($postedData, 'message', ''))),
821 - 'phone' => sanitize_textarea_field(Arr::get($postedData, 'phone_number', '')),
729 + 'phone' => sanitize_text_field(Arr::get($postedData, 'phone_number', '')),
822 730 'address' => sanitize_textarea_field(Arr::get($postedData, 'address', '')),
823 731 'ip_address' => Helper::getIp(),
824 732 'status' => 'scheduled',
825 733 'source' => 'web',
@@ -824,16 +732,16 @@
824 732 'status' => 'scheduled',
825 733 'source' => 'web',
826 734 'event_type' => $calendarEvent->event_type,
827 735 'slot_minutes' => $duration,
828 - 'utm_source' => SanitizeService::sanitizeUtmData(Arr::get($postedData, 'utm_source', '')),
829 - 'utm_medium' => SanitizeService::sanitizeUtmData(Arr::get($postedData, 'utm_medium', '')),
830 - 'utm_campaign' => SanitizeService::sanitizeUtmData(Arr::get($postedData, 'utm_campaign', '')),
831 - 'utm_term' => SanitizeService::sanitizeUtmData(Arr::get($postedData, 'utm_term', '')),
832 - 'utm_content' => SanitizeService::sanitizeUtmData(Arr::get($postedData, 'utm_content', ''))
833 - ];
736 + 'utm_source' => sanitize_text_field(Arr::get($postedData, 'utm_source', '')),
737 + 'utm_medium' => sanitize_text_field(Arr::get($postedData, 'utm_medium', '')),
738 + 'utm_campaign' => sanitize_text_field(Arr::get($postedData, 'utm_campaign', '')),
739 + 'utm_term' => sanitize_text_field(Arr::get($postedData, 'utm_term', '')),
740 + 'utm_content' => sanitize_text_field(Arr::get($postedData, 'utm_content', ''))
741 + ], $postedData, $calendarEvent);
834 742
835 - if ($calendarEvent->isConfirmationRequired($startDateTime)) {
743 + if ($calendarEvent->isConfirmationRequired($bookingData['start_time'])) {
836 744 $bookingData['status'] = 'pending';
837 745 }
838 746
839 747 $locationConfig = Arr::get($postedData, 'location_config', []);
@@ -855,8 +763,12 @@
855 763 if (!empty($postedData['payment_method'])) {
856 764 $customFieldsData['payment_method'] = sanitize_text_field($postedData['payment_method']);
857 765 }
858 766
767 + if (!empty($postedData['recurring_count'])) {
768 + $bookingData['recurring_count'] = (int) Arr::get($postedData, 'recurring_count', 0);
769 + }
770 +
859 771 $timeSlotService = TimeSlotServiceHandler::initService($calendarEvent->calendar, $calendarEvent);
860 772
861 773 if (is_wp_error($timeSlotService)) {
862 774 return TimeSlotServiceHandler::sendError($timeSlotService, $calendarEvent, $timezone);
@@ -861,10 +773,12 @@
861 773 if (is_wp_error($timeSlotService)) {
862 774 return TimeSlotServiceHandler::sendError($timeSlotService, $calendarEvent, $timezone);
863 775 }
864 776
865 - $availableSpot = $timeSlotService->isSpotAvailable($startDateTime, $endDateTime, $duration);
777 + $isSlotLocked = Helper::lockRoundRobinSlot($calendarEvent, $bookingData['start_time'], $bookingData['end_time']);
866 778
779 + $availableSpot = $isSlotLocked ? $timeSlotService->isSpotAvailable($bookingData['start_time'], $bookingData['end_time'], $duration) : false;
780 +
867 781 if (!$availableSpot) {
868 782 wp_send_json([
869 783 'message' => __('This selected time slot is not available. Maybe someone booked the spot just a few seconds ago.', 'fluent-booking')
870 784 ], 422);
@@ -911,26 +825,52 @@
911 825 'booking_hash' => $booking->hash
912 826 ], $booking), 200);
913 827 }
914 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 +
915 841 public function ajaxGetAvailableDates()
916 842 {
843 + if (!Helper::checkRateLimit('available_dates', 30)) {
844 + wp_send_json_error(['message' => __('Too many requests. Please try again in a minute.', 'fluent-booking')], 429);
845 + }
846 +
917 847 $startBenchmark = microtime(true);
918 848
919 849 $request = $_REQUEST; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
920 850
921 - $eventId = (int)$request['event_id'];
851 + $eventId = (int)Arr::get($request, 'event_id');
922 852
923 - $rescheduling = Arr::get($request, 'rescheduling', 'no');
853 + $reschedulingHash = sanitize_text_field(Arr::get($request, 'rescheduling_hash', '')); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
924 854
925 - $calendarEvent = CalendarSlot::findOrfail($eventId);
855 + $calendarEvent = $eventId ? CalendarSlot::find($eventId) : null;
926 856
927 - if (!$calendarEvent || ($calendarEvent->status != 'active' && $rescheduling == 'no')) {
857 + if (!$calendarEvent) {
928 858 wp_send_json([
929 859 'message' => __('Sorry, the host is not accepting any new bookings at the moment.', 'fluent-booking')
930 860 ], 422);
931 861 }
932 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 +
933 873 $calendar = $calendarEvent->calendar;
934 874 $startDate = sanitize_text_field(Arr::get($request, 'start_date')); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
935 875
936 876 if (!$startDate) {
@@ -949,9 +889,9 @@
949 889
950 890 $duration = (int)$calendarEvent->getDuration(Arr::get($request, 'duration', null));
951 891
952 892 $timeSlotService = TimeSlotServiceHandler::initService($calendar, $calendarEvent);
953 -
893 +
954 894 if (is_wp_error($timeSlotService)) {
955 895 return TimeSlotServiceHandler::sendError($timeSlotService, $calendarEvent, $timeZone);
956 896 }
957 897
@@ -960,9 +900,9 @@
960 900 if (is_wp_error($availableSpots)) {
961 901 return TimeSlotServiceHandler::sendError($availableSpots, $calendarEvent, $timeZone);
962 902 }
963 903
964 - $availableSpots = array_filter((array)$availableSpots);
904 + $availableSpots = array_filter((array) $availableSpots);
965 905 $availableSpots = apply_filters('fluent_booking/available_slots_for_view', $availableSpots, $calendarEvent, $calendar, $timeZone, $duration);
966 906
967 907 wp_send_json([
968 908 'available_slots' => $availableSpots,
@@ -973,8 +913,20 @@
973 913 }
974 914
975 915 public function getCalendarEventVars(Calendar $calendar, CalendarSlot $calendarEvent)
976 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 +
977 929 $calendarEvent->description = wpautop($calendarEvent->description);
978 930 $calendarEvent->location_icon_html = $calendarEvent->defaultLocationHtml();
979 931 $formFields = BookingFieldService::getBookingFields($calendarEvent);
980 932
@@ -985,16 +937,16 @@
985 937 'min_bookable_date' => $calendarEvent->getMinBookableDateTime(),
986 938 'is_display_spots' => $calendarEvent->isDisplaySpots(),
987 939 'duration' => $calendarEvent->getDefaultDuration(),
988 940 'title' => $calendarEvent->title,
989 - 'location_settings' => $calendarEvent->location_settings,
941 + 'location_settings' => LocationService::sanitizePublicLocationSettings($calendarEvent->location_settings),
990 942 'location_icon_html' => $calendarEvent->location_icon_html,
991 943 'description' => $calendarEvent->description,
992 944 'pre_selects' => null,
993 - 'settings' => $calendarEvent->settings,
945 + 'settings' => $this->sanitizePublicEventSettings($calendarEvent->settings),
994 946 'type' => $calendarEvent->type,
995 947 'event_type' => $calendarEvent->event_type,
996 - 'time_format' => Arr::get(get_option('_fluent_booking_settings'), 'time_format', '12'),
948 + 'time_format' => $globalConfig['time_format'],
997 949 ];
998 950
999 951 $author = $calendar->getAuthorProfile(true);
1000 952 $author['name'] = $calendar->title;
@@ -1007,12 +959,13 @@
1007 959 'Schedule_Meeting' => __('Schedule Meeting', 'fluent-booking'),
1008 960 'Continue_to_Payments' => __('Continue to Payments', 'fluent-booking'),
1009 961 'Confirm_Payment' => __('Confirm Payment', 'fluent-booking'),
1010 962 ],
1011 - 'date_formatter' => DateTimeHelper::getDateFormatter(true),
1012 - 'isRtl' => Helper::fluentbooking_is_rtl(),
1013 - 'duration_lookup' => Helper::getDurationLookup(),
1014 - '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']
1015 968 ];
1016 969
1017 970 $eventVars['form_fields'] = array_values($eventVars['form_fields']);
1018 971
@@ -1022,10 +975,41 @@
1022 975
1023 976 return apply_filters('fluent_booking/public_event_vars', $eventVars, $calendarEvent);
1024 977 }
1025 978
979 + private function sanitizePublicEventSettings($settings)
980 + {
981 + if (!is_array($settings)) {
982 + return [];
983 + }
984 +
985 + $publicKeys = [
986 + 'recurring_config',
987 + 'multiple_booking',
988 + 'multi_duration',
989 + 'lock_timezone',
990 + 'requires_confirmation',
991 + 'submit_button_text',
992 + ];
993 +
994 + $publicKeys = apply_filters('fluent_booking/public_event_settings_keys', $publicKeys);
995 +
996 + $safe = [];
997 + foreach ($publicKeys as $key) {
998 + if (array_key_exists($key, $settings)) {
999 + $safe[$key] = $settings[$key];
1000 + }
1001 + }
1002 +
1003 + return $safe;
1004 + }
1005 +
1026 1006 public function ajaxHandleCancelMeeting()
1027 1007 {
1008 + if (!Helper::checkRateLimit('cancel_meeting', 15)) {
1009 + wp_send_json_error(['message' => __('Too many requests. Please try again in a minute.', 'fluent-booking')], 429);
1010 + }
1011 +
1028 1012 $data = $_REQUEST; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
1029 1013
1030 1014 $meetingHash = Arr::get($data, 'meeting_hash');
1031 1015