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 +201 -231 1.5.25 → 2.5.0 View file →
@@ -8,16 +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\TimeSlotService;
18 -use FluentBooking\App\Services\PermissionManager;
19 +use FluentBooking\App\Services\RescheduleService;
20 +use FluentBooking\App\Services\CurrenciesHelper;
19 21 use FluentBooking\Framework\Support\Arr;
22 +use FluentBooking\App\Vite;
20 23
21 24 class FrontEndHandler
22 25 {
23 26 public function register()
@@ -69,19 +72,17 @@
69 72 if (!$calendar) {
70 73 return __('Calendar not found', 'fluent-booking');
71 74 }
72 75
73 - $assetUrl = App::getInstance('url.assets');
74 -
75 76 $localizeData = $this->getCalendarEventVars($calendar, $calendarEvent);
76 77 $localizeData['disable_author'] = $atts['disable_author'] == 'yes';
77 78 $localizeData['theme'] = $atts['theme'];
78 79
79 80 if (BookingFieldService::hasPhoneNumberField($localizeData['form_fields'])) {
80 - 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);
81 82 }
82 83
83 - 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);
84 85
85 86 $this->loadGlobalVars();
86 87 wp_localize_script(
87 88 'fluent-booking-public',
@@ -129,9 +130,9 @@
129 130 $event = CalendarEventService::processEvent($event);
130 131 $calendarEvents[$event->calendar_id][] = $event;
131 132 }
132 133
133 - $calendars = Calendar::query()->whereIn('id', $calendarIds)->get();
134 + $calendars = Calendar::query()->with('metas')->whereIn('id', $calendarIds)->get();
134 135
135 136 foreach ($calendars as $calendar) {
136 137 $calendar->activeEvents = $calendarEvents[$calendar->id] ?? [];
137 138 $eventOrder = $calendar->getMeta('event_order');
@@ -156,9 +157,9 @@
156 157
157 158 public function renderTeamHosts($calendars, $headerConfig = [])
158 159 {
159 160 $wrapperId = 'fcal_team_' . Helper::getNextIndex();
160 - 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);
161 162
162 163 $vars = [];
163 164 foreach ($calendars as $calendar) {
164 165 $hostHtml = (string)(string)\FluentBooking\App\App::getInstance('view')->make('landing.author_html', [
@@ -188,10 +189,9 @@
188 189 }
189 190
190 191 wp_localize_script('fluent-booking-team', $wrapperId, $vars);
191 192
192 - $assetUrl = App::getInstance('url.assets');
193 - 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);
194 194 $this->loadGlobalVars();
195 195
196 196 return App::make('view')->make('public.team_page', [
197 197 'hosts' => $calendars,
@@ -229,15 +229,29 @@
229 229 if (!$calendar) {
230 230 return '';
231 231 }
232 232
233 + $settings = LandingPageHelper::getSettings($calendar, 'public');
234 +
233 235 $calendarEventQuery = CalendarSlot::where('calendar_id', $calendar->id)
234 236 ->where('status', 'active');
235 -
237 +
238 + $enabledEvents = [];
239 + $isEnabledOnly = false;
240 + if ($settings['show_type'] != 'all') {
241 + $isEnabledOnly = true;
242 + $enabledEvents = $settings['enabled_slots'];
243 + }
244 +
236 245 if ($eventIds && $eventIds != 'all') {
237 - $calendarEventQuery->whereIn('id', $eventIds);
246 + $isEnabledOnly = true;
247 + $enabledEvents = !empty($enabledEvents) ? array_intersect($enabledEvents, $eventIds) : $eventIds;
238 248 }
239 249
250 + if (!empty($enabledEvents) || $isEnabledOnly) {
251 + $calendarEventQuery->whereIn('id', $enabledEvents);
252 + }
253 +
240 254 $calendarEvents = $calendarEventQuery->get();
241 255
242 256 if ($calendarEvents->isEmpty()) {
243 257 return '';
@@ -257,10 +271,10 @@
257 271 }
258 272
259 273 public function renderCalendarBlock($calendar, $headerConfig = [])
260 274 {
261 - $wrapperId = 'fcal_team_' . Helper::getNextIndex();
262 - 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);
263 277
264 278 $calendarHtml = (string)(string)\FluentBooking\App\App::getInstance('view')->make('landing.author_html', [
265 279 'author' => $calendar->getAuthorProfile(),
266 280 'calendar' => $calendar,
@@ -287,10 +301,9 @@
287 301 }
288 302
289 303 wp_localize_script('fluent-booking-calendar', $wrapperId, $vars);
290 304
291 - $assetUrl = App::getInstance('url.assets');
292 - 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);
293 306 $this->loadGlobalVars();
294 307
295 308 return App::make('view')->make('public.calendar_page', [
296 309 'calendar' => $calendar,
@@ -297,9 +310,10 @@
297 310 'wrapper_id' => $wrapperId,
298 311 'logo' => Arr::get($headerConfig, 'logo', ''),
299 312 'title' => Arr::get($headerConfig, 'title', ''),
300 313 'description' => Arr::get($headerConfig, 'description', ''),
301 - 'wrapper_class' => Arr::get($headerConfig, 'wrapper_class', '')
314 + 'wrapper_class' => Arr::get($headerConfig, 'wrapper_class', ''),
315 + 'hide_info' => Arr::isTrue($headerConfig, 'hide_info')
302 316 ]);
303 317 }
304 318
305 319 public function handleBookingListsShortcode($atts, $content)
@@ -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));
@@ -334,10 +348,10 @@
334 348 $bookingPeriod = sanitize_text_field(Arr::get($data, 'booking_period', $atts['period']));
335 349
336 350 $bookingQuery = Booking::query()->with('calendar_event')
337 351 ->where('email', $userEmail)
338 - ->orderBy('start_time', 'DESC')
339 - ->applyComputedStatus($bookingPeriod);
352 + ->applyComputedStatus($bookingPeriod)
353 + ->applyBookingOrderByStatus($bookingPeriod);
340 354
341 355 if ($atts['calendar_ids'] != 'all') {
342 356 $atts['calendar_ids'] = array_map('intval', explode(',', $atts['calendar_ids']));
343 357 $bookingQuery->whereIn('calendar_id', $atts['calendar_ids']);
@@ -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,120 +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 - 'This field is required.' => __('This field is required.', 'fluent-booking'),
681 - 'No availability in' => __('No availability in', 'fluent-booking'),
682 - 'View next month' => __('View next month', 'fluent-booking'),
683 - 'View previous month' => __('View previous month', 'fluent-booking'),
684 - 'No_payment_method_description' => __('No activated payment method found. If you are an admin please check the event payment settings', 'fluent-booking'),
685 - 'Please fill up the required data' => __('Please fill up the required data', 'fluent-booking'),
686 - 'Please select a valid payment method' => __('Please select a valid payment method', 'fluent-booking'),
687 - 'Please Select' => __('Please Select', 'fluent-booking'),
688 - 'Something is wrong!' => __('Something is wrong!', 'fluent-booking'),
689 - 'Requires Confirmation' => __('Requires Confirmation', 'fluent-booking'),
690 - ],
691 - '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()
692 565 ];
693 566
694 567 if (isset($_SERVER['HTTP_CF_IPCOUNTRY'])) {
695 - $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
696 569 } else {
697 570 $data['user_country'] = Arr::get($globalSettings, 'administration.default_country', '');
698 571 }
699 572
@@ -701,8 +574,12 @@
701 574 }
702 575
703 576 public function ajaxScheduleMeeting()
704 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 +
705 582 $app = App::getInstance();
706 583
707 584 $postedData = $_REQUEST; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
708 585
@@ -720,12 +597,17 @@
720 597
721 598 do_action('fluent_booking/starting_scheduling_ajax', $postedData);
722 599
723 600 $rules = [
724 - 'name' => 'required',
725 - 'email' => 'required|email',
726 - 'timezone' => 'required',
727 - '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',
728 610 ];
729 611
730 612 $messages = [
731 613 'name.required' => __('Please enter your name', 'fluent-booking'),
@@ -735,9 +617,9 @@
735 617 'start_date.required' => __('Please select a date and time', 'fluent-booking')
736 618 ];
737 619
738 620 if ($calendarEvent->isPhoneRequired()) {
739 - $rules['phone_number'] = 'required';
621 + $rules['phone_number'] = ['required', $this->validPhoneNumberRule()];
740 622 $messages['phone_number.required'] = __('Please provide your phone number', 'fluent-booking');
741 623 } else if ($calendarEvent->isAddressRequired()) {
742 624 $rules['address'] = 'required';
743 625 $messages['address.required'] = __('Please provide your Address', 'fluent-booking');
@@ -748,12 +630,13 @@
748 630 $selectedLocation = LocationService::getLocationDetails($calendarEvent, Arr::get($postedData, 'location_config', []), $postedData);
749 631 $selectedLocationDriver = Arr::get($selectedLocation, 'type');
750 632 // is user input required
751 633 if (in_array($selectedLocationDriver, ['in_person_guest', 'phone_guest'])) {
752 - $rules['location_config.user_location_input'] = 'required';
753 634 if ($selectedLocationDriver == 'in_person_guest') {
635 + $rules['location_config.user_location_input'] = 'required';
754 636 $messages['location_config.user_location_input.required'] = __('Please provide your address', 'fluent-booking');
755 637 } else {
638 + $rules['location_config.user_location_input'] = ['required', $this->validPhoneNumberRule()];
756 639 $messages['location_config.user_location_input.required'] = __('Please provide your phone number', 'fluent-booking');
757 640 }
758 641 }
759 642 }
@@ -759,15 +642,10 @@
759 642 }
760 643
761 644 $duration = (int)$calendarEvent->getDuration(Arr::get($postedData, 'duration', null));
762 645
763 - if ($calendarEvent->isPaymentEnabled($duration)) {
764 - $rules['payment_method'] = 'required';
765 - $messages['payment_method.required'] = __('Please select a valid payment method', 'fluent-booking');
766 - }
767 -
768 646 if ($additionalGuests = Arr::get($postedData, 'guests', [])) {
769 - if (in_array($calendarEvent->event_type, ['group', 'group_event'])) {
647 + if ($calendarEvent->isMultiGuestEvent()) {
770 648 $additionalGuests = $this->sanitize_mapped_data($additionalGuests);
771 649 $additionalGuests = array_values(array_filter($additionalGuests, function ($guest) {
772 650 return Arr::get($guest, 'name') && Arr::get($guest, 'email');
773 651 }));
@@ -795,17 +673,17 @@
795 673 ], $postedData, $calendarEvent);
796 674
797 675 $validator = $app->validator->make($postedData, $validationConfig['rules'], $validationConfig['messages']);
798 676 if ($validator->validate()->fails()) {
677 + $errorMessage = $validator->firstError() ?: __('Please fill up the required data', 'fluent-booking');
799 678 wp_send_json([
800 - 'message' => __('Please fill up the required data', 'fluent-booking'),
679 + 'message' => $errorMessage,
801 680 'errors' => $validator->errors()
802 681 ], 422);
803 - return;
804 682 }
805 683
806 684 $customFieldsData = BookingFieldService::getCustomFieldsData($postedData, $calendarEvent);
807 - $customFieldsData = apply_filters('fluent_booking/schedule_custom_field_data', $customFieldsData, $customFieldsData, $calendarEvent);
685 + $customFieldsData = apply_filters('fluent_booking/schedule_custom_field_data', $customFieldsData, $calendarEvent);
808 686
809 687 if (is_wp_error($customFieldsData)) {
810 688 wp_send_json([
811 689 'message' => $customFieldsData->get_error_message(),
@@ -810,11 +688,18 @@
810 688 wp_send_json([
811 689 'message' => $customFieldsData->get_error_message(),
812 690 'errors' => $customFieldsData->get_error_data()
813 691 ], 422);
814 - return;
815 692 }
816 693
694 + $validateDateFields = BookingFieldService::validateDateFields($customFieldsData, $calendarEvent);
695 +
696 + if (is_wp_error($validateDateFields)) {
697 + wp_send_json([
698 + 'message' => $validateDateFields->get_error_message(),
699 + ], 422);
700 + }
701 +
817 702 $startDate = Arr::get($postedData, 'start_date');
818 703 $timezone = sanitize_text_field(Arr::get($postedData, 'timezone', 'UTC'));
819 704
820 705 if (is_array($startDate)) {
@@ -833,9 +718,9 @@
833 718 $startDateTime = DateTimeHelper::convertToUtc($startDate, $timezone);
834 719 $endDateTime = gmdate('Y-m-d H:i:s', strtotime($startDateTime) + ($duration * 60)); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
835 720 }
836 721
837 - $bookingData = [
722 + $bookingData = apply_filters('fluent_booking/initialize_booking_data', [
838 723 'person_time_zone' => sanitize_text_field($timezone),
839 724 'start_time' => $startDateTime,
840 725 'end_time' => $endDateTime,
841 726 'name' => sanitize_text_field($postedData['name']),
@@ -840,18 +725,23 @@
840 725 'end_time' => $endDateTime,
841 726 'name' => sanitize_text_field($postedData['name']),
842 727 'email' => sanitize_email($postedData['email']),
843 728 'message' => sanitize_textarea_field(wp_unslash(Arr::get($postedData, 'message', ''))),
844 - 'phone' => sanitize_textarea_field(Arr::get($postedData, 'phone_number', '')),
729 + 'phone' => sanitize_text_field(Arr::get($postedData, 'phone_number', '')),
845 730 'address' => sanitize_textarea_field(Arr::get($postedData, 'address', '')),
846 731 'ip_address' => Helper::getIp(),
847 732 'status' => 'scheduled',
848 733 'source' => 'web',
849 734 'event_type' => $calendarEvent->event_type,
850 - 'slot_minutes' => $duration
851 - ];
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);
852 742
853 - if ($calendarEvent->isConfirmationRequired($startDateTime)) {
743 + if ($calendarEvent->isConfirmationRequired($bookingData['start_time'])) {
854 744 $bookingData['status'] = 'pending';
855 745 }
856 746
857 747 $locationConfig = Arr::get($postedData, 'location_config', []);
@@ -865,12 +755,20 @@
865 755 if ($sourceUrl = Arr::get($postedData, 'source_url', '')) {
866 756 $bookingData['source_url'] = sanitize_url($sourceUrl);
867 757 }
868 758
759 + if (!empty($postedData['coupon_codes'])) {
760 + $bookingData['coupon_codes'] = array_map('sanitize_text_field', array_unique($postedData['coupon_codes']));
761 + }
762 +
869 763 if (!empty($postedData['payment_method'])) {
870 - $customFieldsData['payment_method'] = $postedData['payment_method'];
764 + $customFieldsData['payment_method'] = sanitize_text_field($postedData['payment_method']);
871 765 }
872 766
767 + if (!empty($postedData['recurring_count'])) {
768 + $bookingData['recurring_count'] = (int) Arr::get($postedData, 'recurring_count', 0);
769 + }
770 +
873 771 $timeSlotService = TimeSlotServiceHandler::initService($calendarEvent->calendar, $calendarEvent);
874 772
875 773 if (is_wp_error($timeSlotService)) {
876 774 return TimeSlotServiceHandler::sendError($timeSlotService, $calendarEvent, $timezone);
@@ -875,10 +773,12 @@
875 773 if (is_wp_error($timeSlotService)) {
876 774 return TimeSlotServiceHandler::sendError($timeSlotService, $calendarEvent, $timezone);
877 775 }
878 776
879 - $availableSpot = $timeSlotService->isSpotAvailable($startDateTime, $endDateTime, $duration);
777 + $isSlotLocked = Helper::lockRoundRobinSlot($calendarEvent, $bookingData['start_time'], $bookingData['end_time']);
880 778
779 + $availableSpot = $isSlotLocked ? $timeSlotService->isSpotAvailable($bookingData['start_time'], $bookingData['end_time'], $duration) : false;
780 +
881 781 if (!$availableSpot) {
882 782 wp_send_json([
883 783 'message' => __('This selected time slot is not available. Maybe someone booked the spot just a few seconds ago.', 'fluent-booking')
884 784 ], 422);
@@ -893,9 +793,9 @@
893 793 }
894 794 $bookingData['additional_guests'] = array_slice($additionalGuests, 0, $guestLimit);
895 795 }
896 796
897 - if ($calendarEvent->isTeamEvent()) {
797 + if ($calendarEvent->isRoundRobin()) {
898 798 $bookingData['host_user_id'] = $timeSlotService->hostUserId;
899 799 }
900 800
901 801 do_action('fluent_booking/before_creating_schedule', $bookingData, $postedData, $calendarEvent);
@@ -925,26 +825,52 @@
925 825 'booking_hash' => $booking->hash
926 826 ], $booking), 200);
927 827 }
928 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 +
929 841 public function ajaxGetAvailableDates()
930 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 +
931 847 $startBenchmark = microtime(true);
932 848
933 849 $request = $_REQUEST; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
934 850
935 - $eventId = (int)$request['event_id'];
851 + $eventId = (int)Arr::get($request, 'event_id');
936 852
937 - $rescheduling = Arr::get($request, 'rescheduling', 'no');
853 + $reschedulingHash = sanitize_text_field(Arr::get($request, 'rescheduling_hash', '')); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
938 854
939 - $calendarEvent = CalendarSlot::findOrfail($eventId);
855 + $calendarEvent = $eventId ? CalendarSlot::find($eventId) : null;
940 856
941 - if (!$calendarEvent || ($calendarEvent->status != 'active' && $rescheduling == 'no')) {
857 + if (!$calendarEvent) {
942 858 wp_send_json([
943 859 'message' => __('Sorry, the host is not accepting any new bookings at the moment.', 'fluent-booking')
944 860 ], 422);
945 861 }
946 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 +
947 873 $calendar = $calendarEvent->calendar;
948 874 $startDate = sanitize_text_field(Arr::get($request, 'start_date')); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
949 875
950 876 if (!$startDate) {
@@ -963,9 +889,9 @@
963 889
964 890 $duration = (int)$calendarEvent->getDuration(Arr::get($request, 'duration', null));
965 891
966 892 $timeSlotService = TimeSlotServiceHandler::initService($calendar, $calendarEvent);
967 -
893 +
968 894 if (is_wp_error($timeSlotService)) {
969 895 return TimeSlotServiceHandler::sendError($timeSlotService, $calendarEvent, $timeZone);
970 896 }
971 897
@@ -974,9 +900,9 @@
974 900 if (is_wp_error($availableSpots)) {
975 901 return TimeSlotServiceHandler::sendError($availableSpots, $calendarEvent, $timeZone);
976 902 }
977 903
978 - $availableSpots = array_filter($availableSpots);
904 + $availableSpots = array_filter((array) $availableSpots);
979 905 $availableSpots = apply_filters('fluent_booking/available_slots_for_view', $availableSpots, $calendarEvent, $calendar, $timeZone, $duration);
980 906
981 907 wp_send_json([
982 908 'available_slots' => $availableSpots,
@@ -987,8 +913,20 @@
987 913 }
988 914
989 915 public function getCalendarEventVars(Calendar $calendar, CalendarSlot $calendarEvent)
990 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 +
991 929 $calendarEvent->description = wpautop($calendarEvent->description);
992 930 $calendarEvent->location_icon_html = $calendarEvent->defaultLocationHtml();
993 931 $formFields = BookingFieldService::getBookingFields($calendarEvent);
994 932
@@ -999,16 +937,16 @@
999 937 'min_bookable_date' => $calendarEvent->getMinBookableDateTime(),
1000 938 'is_display_spots' => $calendarEvent->isDisplaySpots(),
1001 939 'duration' => $calendarEvent->getDefaultDuration(),
1002 940 'title' => $calendarEvent->title,
1003 - 'location_settings' => $calendarEvent->location_settings,
941 + 'location_settings' => LocationService::sanitizePublicLocationSettings($calendarEvent->location_settings),
1004 942 'location_icon_html' => $calendarEvent->location_icon_html,
1005 943 'description' => $calendarEvent->description,
1006 944 'pre_selects' => null,
1007 - 'settings' => $calendarEvent->settings,
945 + 'settings' => $this->sanitizePublicEventSettings($calendarEvent->settings),
1008 946 'type' => $calendarEvent->type,
1009 947 'event_type' => $calendarEvent->event_type,
1010 - 'time_format' => Arr::get(get_option('_fluent_booking_settings'), 'time_format', '12'),
948 + 'time_format' => $globalConfig['time_format'],
1011 949 ];
1012 950
1013 951 $author = $calendar->getAuthorProfile(true);
1014 952 $author['name'] = $calendar->title;
@@ -1021,12 +959,13 @@
1021 959 'Schedule_Meeting' => __('Schedule Meeting', 'fluent-booking'),
1022 960 'Continue_to_Payments' => __('Continue to Payments', 'fluent-booking'),
1023 961 'Confirm_Payment' => __('Confirm Payment', 'fluent-booking'),
1024 962 ],
1025 - 'date_formatter' => DateTimeHelper::getDateFormatter(true),
1026 - 'isRtl' => Helper::fluentbooking_is_rtl(),
1027 - 'duration_lookup' => Helper::getDurationLookup(),
1028 - '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']
1029 968 ];
1030 969
1031 970 $eventVars['form_fields'] = array_values($eventVars['form_fields']);
1032 971
@@ -1036,10 +975,41 @@
1036 975
1037 976 return apply_filters('fluent_booking/public_event_vars', $eventVars, $calendarEvent);
1038 977 }
1039 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 +
1040 1006 public function ajaxHandleCancelMeeting()
1041 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 +
1042 1012 $data = $_REQUEST; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
1043 1013
1044 1014 $meetingHash = Arr::get($data, 'meeting_hash');
1045 1015
@@ -1070,9 +1040,9 @@
1070 1040 $result = $meeting->cancelMeeting($message, 'guest', get_current_user_id());
1071 1041
1072 1042 if (is_wp_error($result)) {
1073 1043 if (!wp_doing_ajax()) {
1074 - wp_redirect($meeting->getConfirmationUrl());
1044 + wp_safe_redirect($meeting->getConfirmationUrl());
1075 1045 exit();
1076 1046 }
1077 1047
1078 1048 wp_send_json([
@@ -1085,9 +1055,9 @@
1085 1055 'message' => __('Meeting has been cancelled', 'fluent-booking')
1086 1056 ], 200);
1087 1057 }
1088 1058
1089 - wp_redirect($meeting->getConfirmationUrl());
1059 + wp_safe_redirect($meeting->getConfirmationUrl());
1090 1060 exit;
1091 1061 }
1092 1062
1093 1063 private static function sanitize_mapped_data($settings)