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 +187 -227 1.6.0 → 2.5.0 View file →
@@ -8,15 +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;
20 +use FluentBooking\App\Services\CurrenciesHelper;
18 21 use FluentBooking\Framework\Support\Arr;
22 +use FluentBooking\App\Vite;
19 23
20 24 class FrontEndHandler
21 25 {
22 26 public function register()
@@ -68,19 +72,17 @@
68 72 if (!$calendar) {
69 73 return __('Calendar not found', 'fluent-booking');
70 74 }
71 75
72 - $assetUrl = App::getInstance('url.assets');
73 -
74 76 $localizeData = $this->getCalendarEventVars($calendar, $calendarEvent);
75 77 $localizeData['disable_author'] = $atts['disable_author'] == 'yes';
76 78 $localizeData['theme'] = $atts['theme'];
77 79
78 80 if (BookingFieldService::hasPhoneNumberField($localizeData['form_fields'])) {
79 - 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);
80 82 }
81 83
82 - 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);
83 85
84 86 $this->loadGlobalVars();
85 87 wp_localize_script(
86 88 'fluent-booking-public',
@@ -128,9 +130,9 @@
128 130 $event = CalendarEventService::processEvent($event);
129 131 $calendarEvents[$event->calendar_id][] = $event;
130 132 }
131 133
132 - $calendars = Calendar::query()->whereIn('id', $calendarIds)->get();
134 + $calendars = Calendar::query()->with('metas')->whereIn('id', $calendarIds)->get();
133 135
134 136 foreach ($calendars as $calendar) {
135 137 $calendar->activeEvents = $calendarEvents[$calendar->id] ?? [];
136 138 $eventOrder = $calendar->getMeta('event_order');
@@ -155,9 +157,9 @@
155 157
156 158 public function renderTeamHosts($calendars, $headerConfig = [])
157 159 {
158 160 $wrapperId = 'fcal_team_' . Helper::getNextIndex();
159 - 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);
160 162
161 163 $vars = [];
162 164 foreach ($calendars as $calendar) {
163 165 $hostHtml = (string)(string)\FluentBooking\App\App::getInstance('view')->make('landing.author_html', [
@@ -187,10 +189,9 @@
187 189 }
188 190
189 191 wp_localize_script('fluent-booking-team', $wrapperId, $vars);
190 192
191 - $assetUrl = App::getInstance('url.assets');
192 - 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);
193 194 $this->loadGlobalVars();
194 195
195 196 return App::make('view')->make('public.team_page', [
196 197 'hosts' => $calendars,
@@ -228,15 +229,29 @@
228 229 if (!$calendar) {
229 230 return '';
230 231 }
231 232
233 + $settings = LandingPageHelper::getSettings($calendar, 'public');
234 +
232 235 $calendarEventQuery = CalendarSlot::where('calendar_id', $calendar->id)
233 236 ->where('status', 'active');
234 -
237 +
238 + $enabledEvents = [];
239 + $isEnabledOnly = false;
240 + if ($settings['show_type'] != 'all') {
241 + $isEnabledOnly = true;
242 + $enabledEvents = $settings['enabled_slots'];
243 + }
244 +
235 245 if ($eventIds && $eventIds != 'all') {
236 - $calendarEventQuery->whereIn('id', $eventIds);
246 + $isEnabledOnly = true;
247 + $enabledEvents = !empty($enabledEvents) ? array_intersect($enabledEvents, $eventIds) : $eventIds;
237 248 }
238 249
250 + if (!empty($enabledEvents) || $isEnabledOnly) {
251 + $calendarEventQuery->whereIn('id', $enabledEvents);
252 + }
253 +
239 254 $calendarEvents = $calendarEventQuery->get();
240 255
241 256 if ($calendarEvents->isEmpty()) {
242 257 return '';
@@ -256,10 +271,10 @@
256 271 }
257 272
258 273 public function renderCalendarBlock($calendar, $headerConfig = [])
259 274 {
260 - $wrapperId = 'fcal_team_' . Helper::getNextIndex();
261 - wp_enqueue_script('fluent-booking-calendar', App::getInstance('url.assets') . 'public/js/calendar_app.js', [], FLUENT_BOOKING_ASSETS_VERSION, true);
275 + $wrapperId = 'fcal_calendar_' . Helper::getNextIndex();
276 + Vite::enqueueScript('fluent-booking-calendar', 'calendar_app', [], FLUENT_BOOKING_ASSETS_VERSION);
262 277
263 278 $calendarHtml = (string)(string)\FluentBooking\App\App::getInstance('view')->make('landing.author_html', [
264 279 'author' => $calendar->getAuthorProfile(),
265 280 'calendar' => $calendar,
@@ -286,10 +301,9 @@
286 301 }
287 302
288 303 wp_localize_script('fluent-booking-calendar', $wrapperId, $vars);
289 304
290 - $assetUrl = App::getInstance('url.assets');
291 - 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);
292 306 $this->loadGlobalVars();
293 307
294 308 return App::make('view')->make('public.calendar_page', [
295 309 'calendar' => $calendar,
@@ -312,9 +326,9 @@
312 326 'calendar_ids' => 'all',
313 327 'no_bookings' => __('No bookings found', 'fluent-booking'),
314 328 'per_page' => 10
315 329 ], $atts);
316 -
330 +
317 331 $atts['title'] = sanitize_text_field($atts['title']);
318 332 $atts['filter'] = sanitize_text_field($atts['filter']);
319 333 $atts['pagination'] = sanitize_text_field($atts['pagination']);
320 334 $atts['no_bookings'] = sanitize_text_field($atts['no_bookings']);
@@ -319,15 +333,15 @@
319 333 $atts['pagination'] = sanitize_text_field($atts['pagination']);
320 334 $atts['no_bookings'] = sanitize_text_field($atts['no_bookings']);
321 335
322 336 $userData = get_userdata(get_current_user_id());
323 -
337 +
324 338 $userEmail = $userData ? $userData->user_email : null;
325 -
339 +
326 340 if (!$userEmail) {
327 341 return __('Please login to view your bookings', 'fluent-booking');
328 342 }
329 -
343 +
330 344 $data = $_REQUEST; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
331 345
332 346 $perPage = intval(Arr::get($data, 'booking_per_page', $atts['per_page']));
333 347 $currentPage = intval(Arr::get($data, 'booking_page', 1));
@@ -373,9 +387,9 @@
373 387 $periodOptions = Helper::getBookingPeriodOptions();
374 388
375 389 $pageOptions = apply_filters('fluent_booking/booking_per_page_options', [5, 10, 15, 20, 50, 100]);
376 390
377 - 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);
378 392
379 393 return App::make('view')->make('public.bookings', [
380 394 'bookings' => $bookings,
381 395 'attributes' => $atts,
@@ -393,9 +407,9 @@
393 407 if (!isset($_REQUEST['hash'])) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
394 408 return __('Booking hash is missing!', 'fluent-booking');
395 409 }
396 410
397 - $hash = sanitize_text_field($_REQUEST['hash']); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
411 + $hash = isset($_REQUEST['hash']) ? sanitize_text_field(wp_unslash($_REQUEST['hash'])) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
398 412
399 413 return apply_filters('fluent_booking/payment_receipt_html', '', $hash);
400 414 }
401 415
@@ -404,9 +418,9 @@
404 418 if (empty($data['rescheduling_hash'])) {
405 419 return;
406 420 }
407 421
408 - add_filter('fluent_booking/schedule_custom_field_data', function ($array) {
422 + add_filter('fluent_booking/schedule_custom_field_data', function ($data) {
409 423 return [];
410 424 });
411 425
412 426 add_filter('fluent_booking/schedule_validation_rules_data', function ($data, $postedData, $calendarEvent)
@@ -434,71 +448,28 @@
434 448 'message' => __('Invalid rescheduling request', 'fluent-booking')
435 449 ], 422);
436 450 }
437 451
438 - $rescheduleBy = 'guest';
439 - $hostIds = $existingBooking->getHostIds();
440 - if (in_array(get_current_user_id(), $hostIds) || PermissionManager::userCan('manage_all_bookings')) {
441 - $rescheduleBy = 'host';
442 - }
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 + );
443 463
444 - $existingBooking->updateMeta('rescheduled_by_type', $rescheduleBy);
445 -
446 - if ($rescheduleBy == 'guest' && !$existingBooking->canReschedule()) {
464 + if (is_wp_error($result)) {
447 465 wp_send_json([
448 - 'message' => $existingBooking->getRescheduleMessage()
466 + 'message' => $result->get_error_message()
449 467 ], 422);
450 468 }
451 469
452 - if ($bookingData['start_time'] == $existingBooking->start_time) {
453 - wp_send_json([
454 - 'message' => __('Sorry! you can not reschedule to the same time.', 'fluent-booking')
455 - ], 422);
456 - }
470 + $existingBooking = $result;
457 471
458 - $endDateTime = gmdate('Y-m-d H:i:s', strtotime($bookingData['start_time']) + ($existingBooking->slot_minutes * 60)); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
459 -
460 - $previousBooking = clone $existingBooking;
461 -
462 - if ($existingBooking->isMultiGuestBooking()) {
463 - // Need to handle group booking type here
464 - // check for existing group
465 - $parent = Booking::where('status', 'scheduled')
466 - ->where('event_id', $existingBooking->event_id)
467 - ->where('start_time', $bookingData['start_time'])
468 - ->orderBy('id', 'ASC')
469 - ->first();
470 -
471 - if ($parent) {
472 - $existingBooking->group_id = $parent->group_id;
473 - } else {
474 - $existingBooking->group_id = Helper::getNextBookingGroup();
475 - }
476 - }
477 -
478 - $existingBooking->start_time = $bookingData['start_time'];
479 - $existingBooking->person_time_zone = $bookingData['person_time_zone'];
480 - $existingBooking->end_time = $endDateTime;
481 - $existingBooking->save();
482 -
483 - $existingBooking->updateMeta('previous_meeting_time', $previousBooking->start_time);
484 -
485 - $reschedulingMessage = sanitize_textarea_field(Arr::get($postedData, 'rescheduling_reason'));
486 - if ($reschedulingMessage) {
487 - $existingBooking->updateMeta('reschedule_reason', $reschedulingMessage);
488 - }
489 -
490 - do_action('fluent_booking/log_booking_activity', [
491 - 'booking_id' => $existingBooking->id,
492 - 'type' => 'info',
493 - 'status' => 'closed',
494 - 'title' => __('Meeting Rescheduled', 'fluent-booking'),
495 - /* translators: %1$s is the user who rescheduled the meeting, %2$s is the previous date and time in UTC. */
496 - 'description' => sprintf(__('Meeting has been rescheduled by %1$s from Web UI. Previous date time: %2$s (UTC)', 'fluent-booking'), $rescheduleBy, $previousBooking->start_time)
497 - ]);
498 -
499 - do_action('fluent_booking/after_booking_rescheduled', $existingBooking, $previousBooking, $calendarEvent);
500 -
501 472 add_filter('fluent_booking/schedule_receipt_data', function ($data) {
502 473 $data['title'] = __('Your meeting has been rescheduled', 'fluent-booking');
503 474 return $data;
504 475 });
@@ -580,121 +551,22 @@
580 551 $globalSettings = Helper::getGlobalSettings();
581 552 $startDay = Arr::get($globalSettings, 'administration.start_day', 'mon');
582 553
583 554 $data = [
584 - 'ajaxurl' => admin_url('admin-ajax.php'),
585 - 'timezones' => DateTimeHelper::getFlatGroupedTimeZones(),
586 - 'current_person' => $currentPerson,
587 - 'start_day' => $startDay,
588 - 'i18' => [
589 - 'Timezone' => __('Timezone', 'fluent-booking'),
590 - 'Day' => __('Day', 'fluent-booking'),
591 - 'Days' => __('Days', 'fluent-booking'),
592 - 'Hour' => __('Hour', 'fluent-booking'),
593 - 'Hours' => __('Hours', 'fluent-booking'),
594 - 'Minute' => __('Minute', 'fluent-booking'),
595 - 'Minutes' => __('Minutes', 'fluent-booking'),
596 - 'Enter Details' => __('Enter Details', 'fluent-booking'),
597 - 'Summary' => __('Summary', 'fluent-booking'),
598 - 'Payment Details' => __('Payment Details', 'fluent-booking'),
599 - 'Total Payment' => __('Total Payment', 'fluent-booking'),
600 - 'Payment Method' => __('Payment Method', 'fluent-booking'),
601 - 'Pay Now' => __('Pay Now', 'fluent-booking'),
602 - 'processing' => __('Processing', 'fluent-booking'),
603 - 'date_time_config' => [
604 - 'weekdays' => array(
605 - 'sunday' => _x('Sunday', 'calendar day full', 'fluent-booking'),
606 - 'monday' => _x('Monday', 'calendar day full', 'fluent-booking'),
607 - 'tuesday' => _x('Tuesday', 'calendar day full', 'fluent-booking'),
608 - 'wednesday' => _x('Wednesday', 'calendar day full', 'fluent-booking'),
609 - 'thursday' => _x('Thursday', 'calendar day full', 'fluent-booking'),
610 - 'friday' => _x('Friday', 'calendar day full', 'fluent-booking'),
611 - 'saturday' => _x('Saturday', 'calendar day full', 'fluent-booking'),
612 - ),
613 - 'months' => array(
614 - 'January' => _x('January', 'calendar month name full', 'fluent-booking'),
615 - 'February' => _x('February', 'calendar month name full', 'fluent-booking'),
616 - 'March' => _x('March', 'calendar month name full', 'fluent-booking'),
617 - 'April' => _x('April', 'calendar month name full', 'fluent-booking'),
618 - 'May' => _x('May', 'calendar month name full', 'fluent-booking'),
619 - 'June' => _x('June', 'calendar month name full', 'fluent-booking'),
620 - 'July' => _x('July', 'calendar month name full', 'fluent-booking'),
621 - 'August' => _x('August', 'calendar month name full', 'fluent-booking'),
622 - 'September' => _x('September', 'calendar month name full', 'fluent-booking'),
623 - 'October' => _x('October', 'calendar month name full', 'fluent-booking'),
624 - 'November' => _x('November', 'calendar month name full', 'fluent-booking'),
625 - 'December' => _x('December', 'calendar month name full', 'fluent-booking')
626 - ),
627 - 'weekdaysShort' => array(
628 - 'sun' => _x('Sun', 'calendar day short', 'fluent-booking'),
629 - 'mon' => _x('Mon', 'calendar day short', 'fluent-booking'),
630 - 'tue' => _x('Tue', 'calendar day short', 'fluent-booking'),
631 - 'wed' => _x('Wed', 'calendar day short', 'fluent-booking'),
632 - 'thu' => _x('Thu', 'calendar day short', 'fluent-booking'),
633 - 'fri' => _x('Fri', 'calendar day short', 'fluent-booking'),
634 - 'sat' => _x('Sat', 'calendar day short', 'fluent-booking')
635 - ),
636 - 'monthsShort' => array(
637 - 'jan' => _x('Jan', 'calendar month name short', 'fluent-booking'),
638 - 'feb' => _x('Feb', 'calendar month name short', 'fluent-booking'),
639 - 'mar' => _x('Mar', 'calendar month name short', 'fluent-booking'),
640 - 'apr' => _x('Apr', 'calendar month name short', 'fluent-booking'),
641 - 'may' => _x('May', 'calendar month name short', 'fluent-booking'),
642 - 'jun' => _x('Jun', 'calendar month name short', 'fluent-booking'),
643 - 'jul' => _x('Jul', 'calendar month name short', 'fluent-booking'),
644 - 'aug' => _x('Aug', 'calendar month name short', 'fluent-booking'),
645 - 'sep' => _x('Sep', 'calendar month name short', 'fluent-booking'),
646 - 'oct' => _x('Oct', 'calendar month name short', 'fluent-booking'),
647 - 'nov' => _x('Nov', 'calendar month name short', 'fluent-booking'),
648 - 'dec' => _x('Dec', 'calendar month name short', 'fluent-booking')
649 - ),
650 - 'numericSystem' => _x('0_1_2_3_4_5_6_7_8_9', 'calendar numeric system - Sequence must need to maintained', 'fluent-booking'),
651 - ],
652 - 'Country' => __('Country', 'fluent-booking'),
653 - '12h' => _x('12h', 'date time format switch', 'fluent-booking'),
654 - '24h' => _x('24h', 'date time format switch', 'fluent-booking'),
655 - 'spots left' => _x('spots left', 'for how many spots left for available booking', 'fluent-booking'),
656 - 'spots remaining' => _x('spots remaining', 'for how many spots remaining for available booking', 'fluent-booking'),
657 - 'Next' => _x('Next', 'Booking form spot selection', 'fluent-booking'),
658 - 'Select on the Next Step' => __('Select on the Next Step', 'fluent-booking'),
659 - 'location options' => __('location options', 'fluent-booking'),
660 - 'Your address' => __('Your address', 'fluent-booking'),
661 - 'Organizer Phone Number' => __('Organizer Phone Number', 'fluent-booking'),
662 - 'In Person (Attendee Address)' => __('In Person (Attendee Address)', 'fluent-booking'),
663 - 'In Person (Organizer Address)' => __('In Person (Organizer Address)', 'fluent-booking'),
664 - 'Attendee Phone Number' => __('Attendee Phone Number', 'fluent-booking'),
665 - 'Google Meet' => __('Google Meet', 'fluent-booking'),
666 - 'Zoom Meeting' => __('Zoom Meeting', 'fluent-booking'),
667 - 'Online Meeting' => __('Online Meeting', 'fluent-booking'),
668 - 'Phone Call' => __('Phone Call', 'fluent-booking'),
669 - 'Processing...' => __('Processing...', 'fluent-booking'),
670 - 'Loading Payment Processor...' => __('Loading Payment Processor...', 'fluent-booking'),
671 - 'PM' => __('PM', 'fluent-booking'),
672 - 'AM' => __('AM', 'fluent-booking'),
673 - 'Email' => __('Email', 'fluent-booking'),
674 - 'Date' => __('Date', 'fluent-booking'),
675 - 'Time' => __('Time', 'fluent-booking'),
676 - 'per guest' => __('per guest', 'fluent-booking'),
677 - 'Add guest' => __('Add guest', 'fluent-booking'),
678 - 'Add guests' => __('Add guests', 'fluent-booking'),
679 - 'Add another' => __('Add another', 'fluent-booking'),
680 - 'Choose File' => __('Choose File', 'fluent-booking'),
681 - 'This field is required.' => __('This field is required.', 'fluent-booking'),
682 - 'No availability in' => __('No availability in', 'fluent-booking'),
683 - 'View next month' => __('View next month', 'fluent-booking'),
684 - 'View previous month' => __('View previous month', 'fluent-booking'),
685 - 'No_payment_method_description' => __('No activated payment method found. If you are an admin please check the event payment settings', 'fluent-booking'),
686 - 'Please fill up the required data' => __('Please fill up the required data', 'fluent-booking'),
687 - 'Please select a valid payment method' => __('Please select a valid payment method', 'fluent-booking'),
688 - 'Please Select' => __('Please Select', 'fluent-booking'),
689 - 'Something is wrong!' => __('Something is wrong!', 'fluent-booking'),
690 - 'Requires Confirmation' => __('Requires Confirmation', 'fluent-booking'),
691 - ],
692 - 'theme' => Arr::get(get_option('_fluent_booking_settings'), 'theme','system-default')
555 + 'ajaxurl' => admin_url('admin-ajax.php'),
556 + 'timezones' => DateTimeHelper::getFlatGroupedTimeZones(),
557 + 'current_person' => $currentPerson,
558 + 'start_day' => $startDay,
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 + ]),
563 + 'theme' => Arr::get(get_option('_fluent_booking_settings'), 'theme', 'system-default'),
564 + 'currency_settings' => CurrenciesHelper::getGlobalCurrencySettings()
693 565 ];
694 566
695 567 if (isset($_SERVER['HTTP_CF_IPCOUNTRY'])) {
696 - $data['user_country'] = sanitize_text_field($_SERVER['HTTP_CF_IPCOUNTRY']); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
568 + $data['user_country'] = isset($_SERVER['HTTP_CF_IPCOUNTRY']) ? sanitize_text_field(wp_unslash($_SERVER['HTTP_CF_IPCOUNTRY'])) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
697 569 } else {
698 570 $data['user_country'] = Arr::get($globalSettings, 'administration.default_country', '');
699 571 }
700 572
@@ -702,8 +574,12 @@
702 574 }
703 575
704 576 public function ajaxScheduleMeeting()
705 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 +
706 582 $app = App::getInstance();
707 583
708 584 $postedData = $_REQUEST; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
709 585
@@ -721,12 +597,17 @@
721 597
722 598 do_action('fluent_booking/starting_scheduling_ajax', $postedData);
723 599
724 600 $rules = [
725 - 'name' => 'required',
726 - 'email' => 'required|email',
727 - 'timezone' => 'required',
728 - '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',
729 610 ];
730 611
731 612 $messages = [
732 613 'name.required' => __('Please enter your name', 'fluent-booking'),
@@ -736,9 +617,9 @@
736 617 'start_date.required' => __('Please select a date and time', 'fluent-booking')
737 618 ];
738 619
739 620 if ($calendarEvent->isPhoneRequired()) {
740 - $rules['phone_number'] = 'required';
621 + $rules['phone_number'] = ['required', $this->validPhoneNumberRule()];
741 622 $messages['phone_number.required'] = __('Please provide your phone number', 'fluent-booking');
742 623 } else if ($calendarEvent->isAddressRequired()) {
743 624 $rules['address'] = 'required';
744 625 $messages['address.required'] = __('Please provide your Address', 'fluent-booking');
@@ -749,12 +630,13 @@
749 630 $selectedLocation = LocationService::getLocationDetails($calendarEvent, Arr::get($postedData, 'location_config', []), $postedData);
750 631 $selectedLocationDriver = Arr::get($selectedLocation, 'type');
751 632 // is user input required
752 633 if (in_array($selectedLocationDriver, ['in_person_guest', 'phone_guest'])) {
753 - $rules['location_config.user_location_input'] = 'required';
754 634 if ($selectedLocationDriver == 'in_person_guest') {
635 + $rules['location_config.user_location_input'] = 'required';
755 636 $messages['location_config.user_location_input.required'] = __('Please provide your address', 'fluent-booking');
756 637 } else {
638 + $rules['location_config.user_location_input'] = ['required', $this->validPhoneNumberRule()];
757 639 $messages['location_config.user_location_input.required'] = __('Please provide your phone number', 'fluent-booking');
758 640 }
759 641 }
760 642 }
@@ -760,13 +642,8 @@
760 642 }
761 643
762 644 $duration = (int)$calendarEvent->getDuration(Arr::get($postedData, 'duration', null));
763 645
764 - if ($calendarEvent->isPaymentEnabled($duration)) {
765 - $rules['payment_method'] = 'required';
766 - $messages['payment_method.required'] = __('Please select a valid payment method', 'fluent-booking');
767 - }
768 -
769 646 if ($additionalGuests = Arr::get($postedData, 'guests', [])) {
770 647 if ($calendarEvent->isMultiGuestEvent()) {
771 648 $additionalGuests = $this->sanitize_mapped_data($additionalGuests);
772 649 $additionalGuests = array_values(array_filter($additionalGuests, function ($guest) {
@@ -796,17 +673,17 @@
796 673 ], $postedData, $calendarEvent);
797 674
798 675 $validator = $app->validator->make($postedData, $validationConfig['rules'], $validationConfig['messages']);
799 676 if ($validator->validate()->fails()) {
677 + $errorMessage = $validator->firstError() ?: __('Please fill up the required data', 'fluent-booking');
800 678 wp_send_json([
801 - 'message' => __('Please fill up the required data', 'fluent-booking'),
679 + 'message' => $errorMessage,
802 680 'errors' => $validator->errors()
803 681 ], 422);
804 - return;
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(),
@@ -811,9 +688,8 @@
811 688 wp_send_json([
812 689 'message' => $customFieldsData->get_error_message(),
813 690 'errors' => $customFieldsData->get_error_data()
814 691 ], 422);
815 - return;
816 692 }
817 693
818 694 $validateDateFields = BookingFieldService::validateDateFields($customFieldsData, $calendarEvent);
819 695
@@ -820,9 +696,8 @@
820 696 if (is_wp_error($validateDateFields)) {
821 697 wp_send_json([
822 698 'message' => $validateDateFields->get_error_message(),
823 699 ], 422);
824 - return;
825 700 }
826 701
827 702 $startDate = Arr::get($postedData, 'start_date');
828 703 $timezone = sanitize_text_field(Arr::get($postedData, 'timezone', 'UTC'));
@@ -843,9 +718,9 @@
843 718 $startDateTime = DateTimeHelper::convertToUtc($startDate, $timezone);
844 719 $endDateTime = gmdate('Y-m-d H:i:s', strtotime($startDateTime) + ($duration * 60)); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
845 720 }
846 721
847 - $bookingData = [
722 + $bookingData = apply_filters('fluent_booking/initialize_booking_data', [
848 723 'person_time_zone' => sanitize_text_field($timezone),
849 724 'start_time' => $startDateTime,
850 725 'end_time' => $endDateTime,
851 726 'name' => sanitize_text_field($postedData['name']),
@@ -850,18 +725,23 @@
850 725 'end_time' => $endDateTime,
851 726 'name' => sanitize_text_field($postedData['name']),
852 727 'email' => sanitize_email($postedData['email']),
853 728 'message' => sanitize_textarea_field(wp_unslash(Arr::get($postedData, 'message', ''))),
854 - 'phone' => sanitize_textarea_field(Arr::get($postedData, 'phone_number', '')),
729 + 'phone' => sanitize_text_field(Arr::get($postedData, 'phone_number', '')),
855 730 'address' => sanitize_textarea_field(Arr::get($postedData, 'address', '')),
856 731 'ip_address' => Helper::getIp(),
857 732 'status' => 'scheduled',
858 733 'source' => 'web',
859 734 'event_type' => $calendarEvent->event_type,
860 - 'slot_minutes' => $duration
861 - ];
735 + 'slot_minutes' => $duration,
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);
862 742
863 - if ($calendarEvent->isConfirmationRequired($startDateTime)) {
743 + if ($calendarEvent->isConfirmationRequired($bookingData['start_time'])) {
864 744 $bookingData['status'] = 'pending';
865 745 }
866 746
867 747 $locationConfig = Arr::get($postedData, 'location_config', []);
@@ -875,12 +755,20 @@
875 755 if ($sourceUrl = Arr::get($postedData, 'source_url', '')) {
876 756 $bookingData['source_url'] = sanitize_url($sourceUrl);
877 757 }
878 758
759 + if (!empty($postedData['coupon_codes'])) {
760 + $bookingData['coupon_codes'] = array_map('sanitize_text_field', array_unique($postedData['coupon_codes']));
761 + }
762 +
879 763 if (!empty($postedData['payment_method'])) {
880 - $customFieldsData['payment_method'] = $postedData['payment_method'];
764 + $customFieldsData['payment_method'] = sanitize_text_field($postedData['payment_method']);
881 765 }
882 766
767 + if (!empty($postedData['recurring_count'])) {
768 + $bookingData['recurring_count'] = (int) Arr::get($postedData, 'recurring_count', 0);
769 + }
770 +
883 771 $timeSlotService = TimeSlotServiceHandler::initService($calendarEvent->calendar, $calendarEvent);
884 772
885 773 if (is_wp_error($timeSlotService)) {
886 774 return TimeSlotServiceHandler::sendError($timeSlotService, $calendarEvent, $timezone);
@@ -885,10 +773,12 @@
885 773 if (is_wp_error($timeSlotService)) {
886 774 return TimeSlotServiceHandler::sendError($timeSlotService, $calendarEvent, $timezone);
887 775 }
888 776
889 - $availableSpot = $timeSlotService->isSpotAvailable($startDateTime, $endDateTime, $duration);
777 + $isSlotLocked = Helper::lockRoundRobinSlot($calendarEvent, $bookingData['start_time'], $bookingData['end_time']);
890 778
779 + $availableSpot = $isSlotLocked ? $timeSlotService->isSpotAvailable($bookingData['start_time'], $bookingData['end_time'], $duration) : false;
780 +
891 781 if (!$availableSpot) {
892 782 wp_send_json([
893 783 'message' => __('This selected time slot is not available. Maybe someone booked the spot just a few seconds ago.', 'fluent-booking')
894 784 ], 422);
@@ -935,26 +825,52 @@
935 825 'booking_hash' => $booking->hash
936 826 ], $booking), 200);
937 827 }
938 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 +
939 841 public function ajaxGetAvailableDates()
940 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 +
941 847 $startBenchmark = microtime(true);
942 848
943 849 $request = $_REQUEST; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
944 850
945 - $eventId = (int)$request['event_id'];
851 + $eventId = (int)Arr::get($request, 'event_id');
946 852
947 - $rescheduling = Arr::get($request, 'rescheduling', 'no');
853 + $reschedulingHash = sanitize_text_field(Arr::get($request, 'rescheduling_hash', '')); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
948 854
949 - $calendarEvent = CalendarSlot::findOrfail($eventId);
855 + $calendarEvent = $eventId ? CalendarSlot::find($eventId) : null;
950 856
951 - if (!$calendarEvent || ($calendarEvent->status != 'active' && $rescheduling == 'no')) {
857 + if (!$calendarEvent) {
952 858 wp_send_json([
953 859 'message' => __('Sorry, the host is not accepting any new bookings at the moment.', 'fluent-booking')
954 860 ], 422);
955 861 }
956 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 +
957 873 $calendar = $calendarEvent->calendar;
958 874 $startDate = sanitize_text_field(Arr::get($request, 'start_date')); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
959 875
960 876 if (!$startDate) {
@@ -973,9 +889,9 @@
973 889
974 890 $duration = (int)$calendarEvent->getDuration(Arr::get($request, 'duration', null));
975 891
976 892 $timeSlotService = TimeSlotServiceHandler::initService($calendar, $calendarEvent);
977 -
893 +
978 894 if (is_wp_error($timeSlotService)) {
979 895 return TimeSlotServiceHandler::sendError($timeSlotService, $calendarEvent, $timeZone);
980 896 }
981 897
@@ -984,9 +900,9 @@
984 900 if (is_wp_error($availableSpots)) {
985 901 return TimeSlotServiceHandler::sendError($availableSpots, $calendarEvent, $timeZone);
986 902 }
987 903
988 - $availableSpots = array_filter($availableSpots);
904 + $availableSpots = array_filter((array) $availableSpots);
989 905 $availableSpots = apply_filters('fluent_booking/available_slots_for_view', $availableSpots, $calendarEvent, $calendar, $timeZone, $duration);
990 906
991 907 wp_send_json([
992 908 'available_slots' => $availableSpots,
@@ -997,8 +913,20 @@
997 913 }
998 914
999 915 public function getCalendarEventVars(Calendar $calendar, CalendarSlot $calendarEvent)
1000 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 +
1001 929 $calendarEvent->description = wpautop($calendarEvent->description);
1002 930 $calendarEvent->location_icon_html = $calendarEvent->defaultLocationHtml();
1003 931 $formFields = BookingFieldService::getBookingFields($calendarEvent);
1004 932
@@ -1009,16 +937,16 @@
1009 937 'min_bookable_date' => $calendarEvent->getMinBookableDateTime(),
1010 938 'is_display_spots' => $calendarEvent->isDisplaySpots(),
1011 939 'duration' => $calendarEvent->getDefaultDuration(),
1012 940 'title' => $calendarEvent->title,
1013 - 'location_settings' => $calendarEvent->location_settings,
941 + 'location_settings' => LocationService::sanitizePublicLocationSettings($calendarEvent->location_settings),
1014 942 'location_icon_html' => $calendarEvent->location_icon_html,
1015 943 'description' => $calendarEvent->description,
1016 944 'pre_selects' => null,
1017 - 'settings' => $calendarEvent->settings,
945 + 'settings' => $this->sanitizePublicEventSettings($calendarEvent->settings),
1018 946 'type' => $calendarEvent->type,
1019 947 'event_type' => $calendarEvent->event_type,
1020 - 'time_format' => Arr::get(get_option('_fluent_booking_settings'), 'time_format', '12'),
948 + 'time_format' => $globalConfig['time_format'],
1021 949 ];
1022 950
1023 951 $author = $calendar->getAuthorProfile(true);
1024 952 $author['name'] = $calendar->title;
@@ -1031,12 +959,13 @@
1031 959 'Schedule_Meeting' => __('Schedule Meeting', 'fluent-booking'),
1032 960 'Continue_to_Payments' => __('Continue to Payments', 'fluent-booking'),
1033 961 'Confirm_Payment' => __('Confirm Payment', 'fluent-booking'),
1034 962 ],
1035 - 'date_formatter' => DateTimeHelper::getDateFormatter(true),
1036 - 'isRtl' => Helper::fluentbooking_is_rtl(),
1037 - 'duration_lookup' => Helper::getDurationLookup(),
1038 - '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']
1039 968 ];
1040 969
1041 970 $eventVars['form_fields'] = array_values($eventVars['form_fields']);
1042 971
@@ -1046,10 +975,41 @@
1046 975
1047 976 return apply_filters('fluent_booking/public_event_vars', $eventVars, $calendarEvent);
1048 977 }
1049 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 +
1050 1006 public function ajaxHandleCancelMeeting()
1051 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 +
1052 1012 $data = $_REQUEST; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
1053 1013
1054 1014 $meetingHash = Arr::get($data, 'meeting_hash');
1055 1015
@@ -1080,9 +1040,9 @@
1080 1040 $result = $meeting->cancelMeeting($message, 'guest', get_current_user_id());
1081 1041
1082 1042 if (is_wp_error($result)) {
1083 1043 if (!wp_doing_ajax()) {
1084 - wp_redirect($meeting->getConfirmationUrl());
1044 + wp_safe_redirect($meeting->getConfirmationUrl());
1085 1045 exit();
1086 1046 }
1087 1047
1088 1048 wp_send_json([
@@ -1095,9 +1055,9 @@
1095 1055 'message' => __('Meeting has been cancelled', 'fluent-booking')
1096 1056 ], 200);
1097 1057 }
1098 1058
1099 - wp_redirect($meeting->getConfirmationUrl());
1059 + wp_safe_redirect($meeting->getConfirmationUrl());
1100 1060 exit;
1101 1061 }
1102 1062
1103 1063 private static function sanitize_mapped_data($settings)