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