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 +285 -334 1.5.20 → 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;
17 +use FluentBooking\App\Services\CalendarEventService;
15 18 use FluentBooking\App\Services\LocationService;
16 -use FluentBooking\App\Services\TimeSlotService;
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()
@@ -39,127 +43,9 @@
39 43
40 44 add_action('wp_ajax_fluent_cal_get_available_dates', [$this, 'ajaxGetAvailableDates']);
41 45 add_action('wp_ajax_nopriv_fluent_cal_get_available_dates', [$this, 'ajaxGetAvailableDates']);
42 46
43 - /*
44 - * Rescheduing Handlers
45 - */
46 - add_action('fluent_booking/starting_scheduling_ajax', function ($data) {
47 - if (empty($data['rescheduling_hash'])) {
48 - return;
49 - }
50 -
51 - add_filter('fluent_booking/schedule_custom_field_data', function ($array) {
52 - return [];
53 - });
54 -
55 - add_filter('fluent_booking/schedule_validation_rules_data', function ($data, $postedData, $calendarEvent)
56 - {
57 - $rules = $messages = [];
58 - $rescheduleField = BookingFieldService::getBookingFieldByName($calendarEvent, 'rescheduling_reason');
59 -
60 - if (Arr::isTrue($rescheduleField, 'required')) {
61 - $rules['rescheduling_reason'] = 'required';
62 - $messages['rescheduling_reason.required'] = __('Please provide a rescheduling reason', 'fluent-booking');
63 - }
64 -
65 - return [
66 - 'rules' => $rules,
67 - 'messages' => $messages
68 - ];
69 - }, 10, 3);
70 -
71 - add_action('fluent_booking/before_creating_schedule', function ($bookingData, $postedData, $calendarEvent) {
72 - $existingHash = Arr::get($postedData, 'rescheduling_hash');
73 - $existingBooking = Booking::where('hash', $existingHash)->first();
74 -
75 - if (!$existingBooking) {
76 - wp_send_json([
77 - 'message' => __('Invalid rescheduling request', 'fluent-booking')
78 - ], 422);
79 - }
80 -
81 - $rescheduleBy = 'guest';
82 - $hostIds = $existingBooking->getHostIds();
83 - if (in_array(get_current_user_id(), $hostIds) || PermissionManager::userCan('manage_all_bookings')) {
84 - $rescheduleBy = 'host';
85 - }
86 -
87 - $existingBooking->updateMeta('rescheduled_by_type', $rescheduleBy);
88 -
89 - if ($rescheduleBy == 'guest' && !$existingBooking->canReschedule()) {
90 - wp_send_json([
91 - 'message' => $existingBooking->getRescheduleMessage()
92 - ], 422);
93 - }
94 -
95 - if ($bookingData['start_time'] == $existingBooking->start_time) {
96 - wp_send_json([
97 - 'message' => __('Sorry! you can not reschedule to the same time.', 'fluent-booking')
98 - ], 422);
99 - }
100 -
101 - $endDateTime = gmdate('Y-m-d H:i:s', strtotime($bookingData['start_time']) + ($existingBooking->slot_minutes * 60)); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
102 -
103 - $previousBooking = clone $existingBooking;
104 -
105 - if ($existingBooking->isMultiGuestBooking()) {
106 - // Need to handle group booking type here
107 - // check for existing group
108 - $parent = Booking::where('status', 'scheduled')
109 - ->where('event_id', $existingBooking->event_id)
110 - ->where('start_time', $bookingData['start_time'])
111 - ->orderBy('id', 'ASC')
112 - ->first();
113 -
114 - if ($parent) {
115 - $existingBooking->group_id = $parent->group_id;
116 - } else {
117 - $existingBooking->group_id = Helper::getNextBookingGroup();
118 - }
119 - }
120 -
121 - $existingBooking->start_time = $bookingData['start_time'];
122 - $existingBooking->person_time_zone = $bookingData['person_time_zone'];
123 - $existingBooking->end_time = $endDateTime;
124 - $existingBooking->save();
125 -
126 - $existingBooking->updateMeta('previous_meeting_time', $previousBooking->start_time);
127 -
128 - $reschedulingMessage = sanitize_textarea_field(Arr::get($postedData, 'rescheduling_reason'));
129 - if ($reschedulingMessage) {
130 - $existingBooking->updateMeta('reschedule_reason', $reschedulingMessage);
131 - }
132 -
133 - do_action('fluent_booking/log_booking_activity', [
134 - 'booking_id' => $existingBooking->id,
135 - 'type' => 'info',
136 - 'status' => 'closed',
137 - 'title' => __('Meeting Rescheduled', 'fluent-booking'),
138 - /* translators: %1$s is the user who rescheduled the meeting, %2$s is the previous date and time in UTC. */
139 - 'description' => sprintf(__('Meeting has been rescheduled by %1$s from Web UI. Previous date time: %2$s (UTC)', 'fluent-booking'), $rescheduleBy, $previousBooking->start_time)
140 - ]);
141 -
142 - do_action('fluent_booking/after_booking_rescheduled', $existingBooking, $previousBooking, $calendarEvent);
143 -
144 - add_filter('fluent_booking/schedule_receipt_data', function ($data) {
145 - $data['title'] = __('Your meeting has been rescheduled', 'fluent-booking');
146 - return $data;
147 - });
148 -
149 - $redirectUrl = $existingBooking->getRedirectUrlWithQuery();
150 -
151 - $html = BookingService::getBookingConfirmationHtml($existingBooking);
152 -
153 - wp_send_json([
154 - 'message' => __('Booking has been rescheduled', 'fluent-booking'),
155 - 'redirect_url' => $redirectUrl,
156 - 'response_html' => $html,
157 - 'booking_hash' => $existingBooking->hash
158 - ], 200);
159 -
160 - }, 10, 3);
161 - });
47 + add_action('fluent_booking/starting_scheduling_ajax', [$this, 'handleRescheduling']);
162 48 }
163 49
164 50 public function handleBookingShortcode($atts, $content)
165 51 {
@@ -164,19 +50,23 @@
164 50 public function handleBookingShortcode($atts, $content)
165 51 {
166 52 $atts = shortcode_atts([
167 53 'id' => 0,
54 + 'theme' => 'light',
168 55 'disable_author' => 'no',
169 - 'theme' => 'light'
56 + 'hash' => ''
170 57 ], $atts);
171 58
172 - if (!$atts['id']) {
59 + if (!$atts['id'] && !$atts['hash']) {
173 60 return '';
174 61 }
175 62
176 - $calendarEvent = CalendarSlot::query()->find($atts['id']);
63 + $calendarEvent = CalendarSlot::find($atts['id']);
177 64 if (!$calendarEvent) {
178 - return '';
65 + $calendarEvent = CalendarSlot::where('hash', $atts['hash'])->first();
66 + if (!$calendarEvent) {
67 + return __('Calendar event not found', 'fluent-booking');
68 + }
179 69 }
180 70
181 71 $calendar = $calendarEvent->calendar;
182 72 if (!$calendar) {
@@ -182,29 +72,17 @@
182 72 if (!$calendar) {
183 73 return __('Calendar not found', 'fluent-booking');
184 74 }
185 75
186 - $assetUrl = App::getInstance('url.assets');
187 -
188 76 $localizeData = $this->getCalendarEventVars($calendar, $calendarEvent);
189 77 $localizeData['disable_author'] = $atts['disable_author'] == 'yes';
190 78 $localizeData['theme'] = $atts['theme'];
191 79
192 80 if (BookingFieldService::hasPhoneNumberField($localizeData['form_fields'])) {
193 - wp_enqueue_script('fluent-booking-phone-field', App::getInstance('url.assets') . 'public/js/phone-field.js', [], FLUENT_BOOKING_ASSETS_VERSION, true);
194 - add_action('fluent_booking/short_code_render', function () use ($assetUrl) {
195 - ?>
196 - <style>
197 - .fcal_phone_wrapper .flag {
198 - background: url(<?php echo esc_url($assetUrl.'images/flags_responsive.png'); ?>) no-repeat;
199 - background-size: 100%;
200 - }
201 - </style>
202 - <?php
203 - });
81 + Vite::enqueueScript('fluent-booking-phone-field', 'phone_field', [], FLUENT_BOOKING_ASSETS_VERSION);
204 82 }
205 83
206 - 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);
207 85
208 86 $this->loadGlobalVars();
209 87 wp_localize_script(
210 88 'fluent-booking-public',
@@ -248,17 +126,13 @@
248 126 $calendarIds[] = $event->calendar_id;
249 127 if (!isset($calendarEvents[$event->calendar_id])) {
250 128 $calendarEvents[$event->calendar_id] = [];
251 129 }
252 - $event->payment_html = $event->getPaymentHtml();
253 - $event->durations = $event->getAvailableDurations();
254 - $event->description = $event->getDescription();
255 - $event->short_description = Helper::excerpt($event->description);
256 - $event->locations = $event->defaultLocationHtml();
130 + $event = CalendarEventService::processEvent($event);
257 131 $calendarEvents[$event->calendar_id][] = $event;
258 132 }
259 133
260 - $calendars = Calendar::query()->whereIn('id', $calendarIds)->get();
134 + $calendars = Calendar::query()->with('metas')->whereIn('id', $calendarIds)->get();
261 135
262 136 foreach ($calendars as $calendar) {
263 137 $calendar->activeEvents = $calendarEvents[$calendar->id] ?? [];
264 138 $eventOrder = $calendar->getMeta('event_order');
@@ -283,9 +157,9 @@
283 157
284 158 public function renderTeamHosts($calendars, $headerConfig = [])
285 159 {
286 160 $wrapperId = 'fcal_team_' . Helper::getNextIndex();
287 - 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);
288 162
289 163 $vars = [];
290 164 foreach ($calendars as $calendar) {
291 165 $hostHtml = (string)(string)\FluentBooking\App\App::getInstance('view')->make('landing.author_html', [
@@ -315,10 +189,9 @@
315 189 }
316 190
317 191 wp_localize_script('fluent-booking-team', $wrapperId, $vars);
318 192
319 - $assetUrl = App::getInstance('url.assets');
320 - 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);
321 194 $this->loadGlobalVars();
322 195
323 196 return App::make('view')->make('public.team_page', [
324 197 'hosts' => $calendars,
@@ -356,15 +229,29 @@
356 229 if (!$calendar) {
357 230 return '';
358 231 }
359 232
233 + $settings = LandingPageHelper::getSettings($calendar, 'public');
234 +
360 235 $calendarEventQuery = CalendarSlot::where('calendar_id', $calendar->id)
361 236 ->where('status', 'active');
362 -
237 +
238 + $enabledEvents = [];
239 + $isEnabledOnly = false;
240 + if ($settings['show_type'] != 'all') {
241 + $isEnabledOnly = true;
242 + $enabledEvents = $settings['enabled_slots'];
243 + }
244 +
363 245 if ($eventIds && $eventIds != 'all') {
364 - $calendarEventQuery->whereIn('id', $eventIds);
246 + $isEnabledOnly = true;
247 + $enabledEvents = !empty($enabledEvents) ? array_intersect($enabledEvents, $eventIds) : $eventIds;
365 248 }
366 249
250 + if (!empty($enabledEvents) || $isEnabledOnly) {
251 + $calendarEventQuery->whereIn('id', $enabledEvents);
252 + }
253 +
367 254 $calendarEvents = $calendarEventQuery->get();
368 255
369 256 if ($calendarEvents->isEmpty()) {
370 257 return '';
@@ -369,25 +256,10 @@
369 256 if ($calendarEvents->isEmpty()) {
370 257 return '';
371 258 }
372 259
373 - $eventOrder = $calendar->getMeta('event_order');
260 + $calendarEvents = CalendarEventService::processEvents($calendar, $calendarEvents);
374 261
375 - if (!empty($eventOrder)) {
376 - $calendarEvents = $calendarEvents->sortBy(function($event) use ($eventOrder) {
377 - return array_search($event->id, $eventOrder);
378 - })->values();
379 - }
380 -
381 - foreach ($calendarEvents as $event) {
382 - $event->payment_html = $event->getPaymentHtml();
383 - $event->public_url = $event->getPublicUrl();
384 - $event->durations = $event->getAvailableDurations();
385 - $event->description = $event->getDescription();
386 - $event->short_description = Helper::excerpt($event->description);
387 - $event->locations = $event->defaultLocationHtml();
388 - }
389 -
390 262 $calendar->activeEvents = $calendarEvents;
391 263
392 264 return $this->renderCalendarBlock($calendar, [
393 265 'title' => $title,
@@ -399,10 +271,10 @@
399 271 }
400 272
401 273 public function renderCalendarBlock($calendar, $headerConfig = [])
402 274 {
403 - $wrapperId = 'fcal_team_' . Helper::getNextIndex();
404 - 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);
405 277
406 278 $calendarHtml = (string)(string)\FluentBooking\App\App::getInstance('view')->make('landing.author_html', [
407 279 'author' => $calendar->getAuthorProfile(),
408 280 'calendar' => $calendar,
@@ -429,10 +301,9 @@
429 301 }
430 302
431 303 wp_localize_script('fluent-booking-calendar', $wrapperId, $vars);
432 304
433 - $assetUrl = App::getInstance('url.assets');
434 - 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);
435 306 $this->loadGlobalVars();
436 307
437 308 return App::make('view')->make('public.calendar_page', [
438 309 'calendar' => $calendar,
@@ -439,9 +310,10 @@
439 310 'wrapper_id' => $wrapperId,
440 311 'logo' => Arr::get($headerConfig, 'logo', ''),
441 312 'title' => Arr::get($headerConfig, 'title', ''),
442 313 'description' => Arr::get($headerConfig, 'description', ''),
443 - 'wrapper_class' => Arr::get($headerConfig, 'wrapper_class', '')
314 + 'wrapper_class' => Arr::get($headerConfig, 'wrapper_class', ''),
315 + 'hide_info' => Arr::isTrue($headerConfig, 'hide_info')
444 316 ]);
445 317 }
446 318
447 319 public function handleBookingListsShortcode($atts, $content)
@@ -454,9 +326,9 @@
454 326 'calendar_ids' => 'all',
455 327 'no_bookings' => __('No bookings found', 'fluent-booking'),
456 328 'per_page' => 10
457 329 ], $atts);
458 -
330 +
459 331 $atts['title'] = sanitize_text_field($atts['title']);
460 332 $atts['filter'] = sanitize_text_field($atts['filter']);
461 333 $atts['pagination'] = sanitize_text_field($atts['pagination']);
462 334 $atts['no_bookings'] = sanitize_text_field($atts['no_bookings']);
@@ -461,15 +333,15 @@
461 333 $atts['pagination'] = sanitize_text_field($atts['pagination']);
462 334 $atts['no_bookings'] = sanitize_text_field($atts['no_bookings']);
463 335
464 336 $userData = get_userdata(get_current_user_id());
465 -
337 +
466 338 $userEmail = $userData ? $userData->user_email : null;
467 -
339 +
468 340 if (!$userEmail) {
469 341 return __('Please login to view your bookings', 'fluent-booking');
470 342 }
471 -
343 +
472 344 $data = $_REQUEST; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
473 345
474 346 $perPage = intval(Arr::get($data, 'booking_per_page', $atts['per_page']));
475 347 $currentPage = intval(Arr::get($data, 'booking_page', 1));
@@ -476,10 +348,10 @@
476 348 $bookingPeriod = sanitize_text_field(Arr::get($data, 'booking_period', $atts['period']));
477 349
478 350 $bookingQuery = Booking::query()->with('calendar_event')
479 351 ->where('email', $userEmail)
480 - ->orderBy('start_time', 'DESC')
481 - ->applyComputedStatus($bookingPeriod);
352 + ->applyComputedStatus($bookingPeriod)
353 + ->applyBookingOrderByStatus($bookingPeriod);
482 354
483 355 if ($atts['calendar_ids'] != 'all') {
484 356 $atts['calendar_ids'] = array_map('intval', explode(',', $atts['calendar_ids']));
485 357 $bookingQuery->whereIn('calendar_id', $atts['calendar_ids']);
@@ -495,9 +367,9 @@
495 367 $booking->booking_status_text = $booking->getBookingStatus();
496 368 $booking->payment_status_text = $booking->getPaymentStatus();
497 369
498 370 $booking->booking_date = DateTimeHelper::formatToLocale($booking->getAttendeeStartTime(), 'date');
499 - $booking->booking_time = DateTimeHelper::formatToLocale($booking->getAttendeeEndTime(), 'time') . ' - ' . DateTimeHelper::formatToLocale($booking->getAttendeeEndTime(), 'time');
371 + $booking->booking_time = DateTimeHelper::formatToLocale($booking->getAttendeeStartTime(), 'time') . ' - ' . DateTimeHelper::formatToLocale($booking->getAttendeeEndTime(), 'time');
500 372 }
501 373
502 374 $currentPage = $bookings->currentPage();
503 375 $lastPage = $bookings->lastPage();
@@ -515,9 +387,9 @@
515 387 $periodOptions = Helper::getBookingPeriodOptions();
516 388
517 389 $pageOptions = apply_filters('fluent_booking/booking_per_page_options', [5, 10, 15, 20, 50, 100]);
518 390
519 - 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);
520 392
521 393 return App::make('view')->make('public.bookings', [
522 394 'bookings' => $bookings,
523 395 'attributes' => $atts,
@@ -535,13 +407,88 @@
535 407 if (!isset($_REQUEST['hash'])) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
536 408 return __('Booking hash is missing!', 'fluent-booking');
537 409 }
538 410
539 - $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
540 412
541 413 return apply_filters('fluent_booking/payment_receipt_html', '', $hash);
542 414 }
543 415
416 + public function handleRescheduling($data)
417 + {
418 + if (empty($data['rescheduling_hash'])) {
419 + return;
420 + }
421 +
422 + add_filter('fluent_booking/schedule_custom_field_data', function ($data) {
423 + return [];
424 + });
425 +
426 + add_filter('fluent_booking/schedule_validation_rules_data', function ($data, $postedData, $calendarEvent)
427 + {
428 + $rules = $messages = [];
429 + $rescheduleField = BookingFieldService::getBookingFieldByName($calendarEvent, 'rescheduling_reason');
430 +
431 + if (Arr::isTrue($rescheduleField, 'required')) {
432 + $rules['rescheduling_reason'] = 'required';
433 + $messages['rescheduling_reason.required'] = __('Please provide a rescheduling reason', 'fluent-booking');
434 + }
435 +
436 + return [
437 + 'rules' => $rules,
438 + 'messages' => $messages
439 + ];
440 + }, 10, 3);
441 +
442 + add_action('fluent_booking/before_creating_schedule', function ($bookingData, $postedData, $calendarEvent) {
443 + $existingHash = Arr::get($postedData, 'rescheduling_hash');
444 + $existingBooking = Booking::where('hash', $existingHash)->first();
445 +
446 + if (!$existingBooking) {
447 + wp_send_json([
448 + 'message' => __('Invalid rescheduling request', 'fluent-booking')
449 + ], 422);
450 + }
451 +
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 + );
463 +
464 + if (is_wp_error($result)) {
465 + wp_send_json([
466 + 'message' => $result->get_error_message()
467 + ], 422);
468 + }
469 +
470 + $existingBooking = $result;
471 +
472 + add_filter('fluent_booking/schedule_receipt_data', function ($data) {
473 + $data['title'] = __('Your meeting has been rescheduled', 'fluent-booking');
474 + return $data;
475 + });
476 +
477 + $redirectUrl = $existingBooking->getRedirectUrlWithQuery();
478 +
479 + $html = BookingService::getBookingConfirmationHtml($existingBooking);
480 +
481 + wp_send_json(apply_filters('fluent_booking/booking_rescheduled_response', [
482 + 'message' => __('Booking has been rescheduled', 'fluent-booking'),
483 + 'redirect_url' => $redirectUrl,
484 + 'response_html' => $html,
485 + 'booking_hash' => $existingBooking->hash
486 + ], $existingBooking), 200);
487 +
488 + }, 10, 3);
489 + }
490 +
544 491 private function loadGlobalVars()
545 492 {
546 493 static $loaded;
547 494
@@ -604,118 +551,22 @@
604 551 $globalSettings = Helper::getGlobalSettings();
605 552 $startDay = Arr::get($globalSettings, 'administration.start_day', 'mon');
606 553
607 554 $data = [
608 - 'ajaxurl' => admin_url('admin-ajax.php'),
609 - 'timezones' => DateTimeHelper::getFlatGroupedTimeZones(),
610 - 'current_person' => $currentPerson,
611 - 'start_day' => $startDay,
612 - 'i18' => [
613 - 'Timezone' => __('Timezone', 'fluent-booking'),
614 - 'Day' => __('Day', 'fluent-booking'),
615 - 'Days' => __('Days', 'fluent-booking'),
616 - 'Hour' => __('Hour', 'fluent-booking'),
617 - 'Hours' => __('Hours', 'fluent-booking'),
618 - 'Minute' => __('Minute', 'fluent-booking'),
619 - 'Minutes' => __('Minutes', 'fluent-booking'),
620 - 'Enter Details' => __('Enter Details', 'fluent-booking'),
621 - 'Summary' => __('Summary', 'fluent-booking'),
622 - 'Payment Details' => __('Payment Details', 'fluent-booking'),
623 - 'Total Payment' => __('Total Payment', 'fluent-booking'),
624 - 'Payment Method' => __('Payment Method', 'fluent-booking'),
625 - 'Pay Now' => __('Pay Now', 'fluent-booking'),
626 - 'processing' => __('Processing', 'fluent-booking'),
627 - 'date_time_config' => [
628 - 'weekdays' => array(
629 - 'sunday' => _x('Sunday', 'calendar day full', 'fluent-booking'),
630 - 'monday' => _x('Monday', 'calendar day full', 'fluent-booking'),
631 - 'tuesday' => _x('Tuesday', 'calendar day full', 'fluent-booking'),
632 - 'wednesday' => _x('Wednesday', 'calendar day full', 'fluent-booking'),
633 - 'thursday' => _x('Thursday', 'calendar day full', 'fluent-booking'),
634 - 'friday' => _x('Friday', 'calendar day full', 'fluent-booking'),
635 - 'saturday' => _x('Saturday', 'calendar day full', 'fluent-booking'),
636 - ),
637 - 'months' => array(
638 - 'January' => _x('January', 'calendar month name full', 'fluent-booking'),
639 - 'February' => _x('February', 'calendar month name full', 'fluent-booking'),
640 - 'March' => _x('March', 'calendar month name full', 'fluent-booking'),
641 - 'April' => _x('April', 'calendar month name full', 'fluent-booking'),
642 - 'May' => _x('May', 'calendar month name full', 'fluent-booking'),
643 - 'June' => _x('June', 'calendar month name full', 'fluent-booking'),
644 - 'July' => _x('July', 'calendar month name full', 'fluent-booking'),
645 - 'August' => _x('August', 'calendar month name full', 'fluent-booking'),
646 - 'September' => _x('September', 'calendar month name full', 'fluent-booking'),
647 - 'October' => _x('October', 'calendar month name full', 'fluent-booking'),
648 - 'November' => _x('November', 'calendar month name full', 'fluent-booking'),
649 - 'December' => _x('December', 'calendar month name full', 'fluent-booking')
650 - ),
651 - 'weekdaysShort' => array(
652 - 'sun' => _x('Sun', 'calendar day short', 'fluent-booking'),
653 - 'mon' => _x('Mon', 'calendar day short', 'fluent-booking'),
654 - 'tue' => _x('Tue', 'calendar day short', 'fluent-booking'),
655 - 'wed' => _x('Wed', 'calendar day short', 'fluent-booking'),
656 - 'thu' => _x('Thu', 'calendar day short', 'fluent-booking'),
657 - 'fri' => _x('Fri', 'calendar day short', 'fluent-booking'),
658 - 'sat' => _x('Sat', 'calendar day short', 'fluent-booking')
659 - ),
660 - 'monthsShort' => array(
661 - 'jan' => _x('Jan', 'calendar month name short', 'fluent-booking'),
662 - 'feb' => _x('Feb', 'calendar month name short', 'fluent-booking'),
663 - 'mar' => _x('Mar', 'calendar month name short', 'fluent-booking'),
664 - 'apr' => _x('Apr', 'calendar month name short', 'fluent-booking'),
665 - 'may' => _x('May', 'calendar month name short', 'fluent-booking'),
666 - 'jun' => _x('Jun', 'calendar month name short', 'fluent-booking'),
667 - 'jul' => _x('Jul', 'calendar month name short', 'fluent-booking'),
668 - 'aug' => _x('Aug', 'calendar month name short', 'fluent-booking'),
669 - 'sep' => _x('Sep', 'calendar month name short', 'fluent-booking'),
670 - 'oct' => _x('Oct', 'calendar month name short', 'fluent-booking'),
671 - 'nov' => _x('Nov', 'calendar month name short', 'fluent-booking'),
672 - 'dec' => _x('Dec', 'calendar month name short', 'fluent-booking')
673 - ),
674 - 'numericSystem' => _x('0_1_2_3_4_5_6_7_8_9', 'calendar numeric system - Sequence must need to maintained', 'fluent-booking'),
675 - ],
676 - 'Country' => __('Country', 'fluent-booking'),
677 - '12h' => _x('12h', 'date time format switch', 'fluent-booking'),
678 - '24h' => _x('24h', 'date time format switch', 'fluent-booking'),
679 - 'spots left' => _x('spots left', 'for how many spots left for available booking', 'fluent-booking'),
680 - 'spots remaining' => _x('spots remaining', 'for how many spots remaining for available booking', 'fluent-booking'),
681 - 'Next' => _x('Next', 'Booking form spot selection', 'fluent-booking'),
682 - 'Select on the Next Step' => __('Select on the Next Step', 'fluent-booking'),
683 - 'location options' => __('location options', 'fluent-booking'),
684 - 'Your address' => __('Your address', 'fluent-booking'),
685 - 'Organizer Phone Number' => __('Organizer Phone Number', 'fluent-booking'),
686 - 'In Person (Attendee Address)' => __('In Person (Attendee Address)', 'fluent-booking'),
687 - 'In Person (Organizer Address)' => __('In Person (Organizer Address)', 'fluent-booking'),
688 - 'Attendee Phone Number' => __('Attendee Phone Number', 'fluent-booking'),
689 - 'Google Meet' => __('Google Meet', 'fluent-booking'),
690 - 'Zoom Meeting' => __('Zoom Meeting', 'fluent-booking'),
691 - 'Online Meeting' => __('Online Meeting', 'fluent-booking'),
692 - 'Phone Call' => __('Phone Call', 'fluent-booking'),
693 - 'Processing...' => __('Processing...', 'fluent-booking'),
694 - 'Loading Payment Processor...' => __('Loading Payment Processor...', 'fluent-booking'),
695 - 'PM' => __('PM', 'fluent-booking'),
696 - 'AM' => __('AM', 'fluent-booking'),
697 - 'Email' => __('Email', 'fluent-booking'),
698 - 'Date' => __('Date', 'fluent-booking'),
699 - 'Time' => __('Time', 'fluent-booking'),
700 - 'Add guests' => __('Add guests', 'fluent-booking'),
701 - 'Add another' => __('Add another', 'fluent-booking'),
702 - 'This field is required.' => __('This field is required.', 'fluent-booking'),
703 - 'No availability in' => __('No availability in', 'fluent-booking'),
704 - 'View next month' => __('View next month', 'fluent-booking'),
705 - 'View previous month' => __('View previous month', 'fluent-booking'),
706 - 'No_payment_method_description' => __('No activated payment method found. If you are an admin please check the event payment settings', 'fluent-booking'),
707 - 'Please fill up the required data' => __('Please fill up the required data', 'fluent-booking'),
708 - 'Please select a valid payment method' => __('Please select a valid payment method', 'fluent-booking'),
709 - 'Please Select' => __('Please Select', 'fluent-booking'),
710 - 'Something is wrong!' => __('Something is wrong!', 'fluent-booking'),
711 - 'Requires Confirmation' => __('Requires Confirmation', 'fluent-booking'),
712 - ],
713 - '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()
714 565 ];
715 566
716 567 if (isset($_SERVER['HTTP_CF_IPCOUNTRY'])) {
717 - $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
718 569 } else {
719 570 $data['user_country'] = Arr::get($globalSettings, 'administration.default_country', '');
720 571 }
721 572
@@ -723,8 +574,12 @@
723 574 }
724 575
725 576 public function ajaxScheduleMeeting()
726 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 +
727 582 $app = App::getInstance();
728 583
729 584 $postedData = $_REQUEST; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
730 585
@@ -735,8 +590,9 @@
735 590 $calendarEvent = CalendarSlot::find($eventId);
736 591
737 592 if (!$calendarEvent || ($calendarEvent->status != 'active' && !$isRescheduling)) {
738 593 wp_send_json([
594 + 'message' => __('Sorry, the host is not accepting any new bookings at the moment.', 'fluent-booking')
739 595 ], 422);
740 596 }
741 597
742 598 do_action('fluent_booking/starting_scheduling_ajax', $postedData);
@@ -741,12 +597,17 @@
741 597
742 598 do_action('fluent_booking/starting_scheduling_ajax', $postedData);
743 599
744 600 $rules = [
745 - 'name' => 'required',
746 - 'email' => 'required|email',
747 - 'timezone' => 'required',
748 - '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',
749 610 ];
750 611
751 612 $messages = [
752 613 'name.required' => __('Please enter your name', 'fluent-booking'),
@@ -756,9 +617,9 @@
756 617 'start_date.required' => __('Please select a date and time', 'fluent-booking')
757 618 ];
758 619
759 620 if ($calendarEvent->isPhoneRequired()) {
760 - $rules['phone_number'] = 'required';
621 + $rules['phone_number'] = ['required', $this->validPhoneNumberRule()];
761 622 $messages['phone_number.required'] = __('Please provide your phone number', 'fluent-booking');
762 623 } else if ($calendarEvent->isAddressRequired()) {
763 624 $rules['address'] = 'required';
764 625 $messages['address.required'] = __('Please provide your Address', 'fluent-booking');
@@ -769,12 +630,13 @@
769 630 $selectedLocation = LocationService::getLocationDetails($calendarEvent, Arr::get($postedData, 'location_config', []), $postedData);
770 631 $selectedLocationDriver = Arr::get($selectedLocation, 'type');
771 632 // is user input required
772 633 if (in_array($selectedLocationDriver, ['in_person_guest', 'phone_guest'])) {
773 - $rules['location_config.user_location_input'] = 'required';
774 634 if ($selectedLocationDriver == 'in_person_guest') {
635 + $rules['location_config.user_location_input'] = 'required';
775 636 $messages['location_config.user_location_input.required'] = __('Please provide your address', 'fluent-booking');
776 637 } else {
638 + $rules['location_config.user_location_input'] = ['required', $this->validPhoneNumberRule()];
777 639 $messages['location_config.user_location_input.required'] = __('Please provide your phone number', 'fluent-booking');
778 640 }
779 641 }
780 642 }
@@ -780,15 +642,10 @@
780 642 }
781 643
782 644 $duration = (int)$calendarEvent->getDuration(Arr::get($postedData, 'duration', null));
783 645
784 - if ($calendarEvent->isPaymentEnabled($duration)) {
785 - $rules['payment_method'] = 'required';
786 - $messages['payment_method.required'] = __('Please select a valid payment method', 'fluent-booking');
787 - }
788 -
789 646 if ($additionalGuests = Arr::get($postedData, 'guests', [])) {
790 - if (in_array($calendarEvent->event_type, ['group', 'group_event'])) {
647 + if ($calendarEvent->isMultiGuestEvent()) {
791 648 $additionalGuests = $this->sanitize_mapped_data($additionalGuests);
792 649 $additionalGuests = array_values(array_filter($additionalGuests, function ($guest) {
793 650 return Arr::get($guest, 'name') && Arr::get($guest, 'email');
794 651 }));
@@ -816,17 +673,17 @@
816 673 ], $postedData, $calendarEvent);
817 674
818 675 $validator = $app->validator->make($postedData, $validationConfig['rules'], $validationConfig['messages']);
819 676 if ($validator->validate()->fails()) {
677 + $errorMessage = $validator->firstError() ?: __('Please fill up the required data', 'fluent-booking');
820 678 wp_send_json([
821 - 'message' => __('Please fill up the required data', 'fluent-booking'),
679 + 'message' => $errorMessage,
822 680 'errors' => $validator->errors()
823 681 ], 422);
824 - return;
825 682 }
826 683
827 684 $customFieldsData = BookingFieldService::getCustomFieldsData($postedData, $calendarEvent);
828 - $customFieldsData = apply_filters('fluent_booking/schedule_custom_field_data', $customFieldsData, $customFieldsData, $calendarEvent);
685 + $customFieldsData = apply_filters('fluent_booking/schedule_custom_field_data', $customFieldsData, $calendarEvent);
829 686
830 687 if (is_wp_error($customFieldsData)) {
831 688 wp_send_json([
832 689 'message' => $customFieldsData->get_error_message(),
@@ -831,11 +688,18 @@
831 688 wp_send_json([
832 689 'message' => $customFieldsData->get_error_message(),
833 690 'errors' => $customFieldsData->get_error_data()
834 691 ], 422);
835 - return;
836 692 }
837 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 +
838 702 $startDate = Arr::get($postedData, 'start_date');
839 703 $timezone = sanitize_text_field(Arr::get($postedData, 'timezone', 'UTC'));
840 704
841 705 if (is_array($startDate)) {
@@ -854,9 +718,9 @@
854 718 $startDateTime = DateTimeHelper::convertToUtc($startDate, $timezone);
855 719 $endDateTime = gmdate('Y-m-d H:i:s', strtotime($startDateTime) + ($duration * 60)); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
856 720 }
857 721
858 - $bookingData = [
722 + $bookingData = apply_filters('fluent_booking/initialize_booking_data', [
859 723 'person_time_zone' => sanitize_text_field($timezone),
860 724 'start_time' => $startDateTime,
861 725 'end_time' => $endDateTime,
862 726 'name' => sanitize_text_field($postedData['name']),
@@ -861,18 +725,23 @@
861 725 'end_time' => $endDateTime,
862 726 'name' => sanitize_text_field($postedData['name']),
863 727 'email' => sanitize_email($postedData['email']),
864 728 'message' => sanitize_textarea_field(wp_unslash(Arr::get($postedData, 'message', ''))),
865 - 'phone' => sanitize_textarea_field(Arr::get($postedData, 'phone_number', '')),
729 + 'phone' => sanitize_text_field(Arr::get($postedData, 'phone_number', '')),
866 730 'address' => sanitize_textarea_field(Arr::get($postedData, 'address', '')),
867 731 'ip_address' => Helper::getIp(),
868 732 'status' => 'scheduled',
869 733 'source' => 'web',
870 734 'event_type' => $calendarEvent->event_type,
871 - 'slot_minutes' => $duration
872 - ];
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);
873 742
874 - if ($calendarEvent->isConfirmationRequired($startDateTime)) {
743 + if ($calendarEvent->isConfirmationRequired($bookingData['start_time'])) {
875 744 $bookingData['status'] = 'pending';
876 745 }
877 746
878 747 $locationConfig = Arr::get($postedData, 'location_config', []);
@@ -886,19 +755,18 @@
886 755 if ($sourceUrl = Arr::get($postedData, 'source_url', '')) {
887 756 $bookingData['source_url'] = sanitize_url($sourceUrl);
888 757 }
889 758
759 + if (!empty($postedData['coupon_codes'])) {
760 + $bookingData['coupon_codes'] = array_map('sanitize_text_field', array_unique($postedData['coupon_codes']));
761 + }
762 +
890 763 if (!empty($postedData['payment_method'])) {
891 - $customFieldsData['payment_method'] = $postedData['payment_method'];
764 + $customFieldsData['payment_method'] = sanitize_text_field($postedData['payment_method']);
892 765 }
893 766
894 - if ($additionalGuests) {
895 - $guestField = BookingFieldService::getBookingFieldByName($calendarEvent, 'guests');
896 - $guestLimit = Arr::get($guestField, 'limit', 10);
897 - if ($calendarEvent->isMultiGuestEvent()) {
898 - $guestLimit--;
899 - }
900 - $bookingData['additional_guests'] = array_slice($additionalGuests, 0, $guestLimit);
767 + if (!empty($postedData['recurring_count'])) {
768 + $bookingData['recurring_count'] = (int) Arr::get($postedData, 'recurring_count', 0);
901 769 }
902 770
903 771 $timeSlotService = TimeSlotServiceHandler::initService($calendarEvent->calendar, $calendarEvent);
904 772
@@ -905,17 +773,29 @@
905 773 if (is_wp_error($timeSlotService)) {
906 774 return TimeSlotServiceHandler::sendError($timeSlotService, $calendarEvent, $timezone);
907 775 }
908 776
909 - $isSpotAvailable = $timeSlotService->isSpotAvailable($startDateTime, $endDateTime, $duration);
777 + $isSlotLocked = Helper::lockRoundRobinSlot($calendarEvent, $bookingData['start_time'], $bookingData['end_time']);
910 778
911 - if (!$isSpotAvailable) {
779 + $availableSpot = $isSlotLocked ? $timeSlotService->isSpotAvailable($bookingData['start_time'], $bookingData['end_time'], $duration) : false;
780 +
781 + if (!$availableSpot) {
912 782 wp_send_json([
913 783 'message' => __('This selected time slot is not available. Maybe someone booked the spot just a few seconds ago.', 'fluent-booking')
914 784 ], 422);
915 785 }
916 786
917 - if ($calendarEvent->isTeamEvent()) {
787 + if ($additionalGuests) {
788 + $guestField = BookingFieldService::getBookingFieldByName($calendarEvent, 'guests');
789 + $guestLimit = Arr::get($guestField, 'limit', 10);
790 + if ($calendarEvent->isMultiGuestEvent()) {
791 + $remaining = Arr::get($availableSpot, 'remaining', $calendarEvent->getMaxBookingPerSlot());
792 + $guestLimit = min($remaining, $guestLimit) - 1;
793 + }
794 + $bookingData['additional_guests'] = array_slice($additionalGuests, 0, $guestLimit);
795 + }
796 +
797 + if ($calendarEvent->isRoundRobin()) {
918 798 $bookingData['host_user_id'] = $timeSlotService->hostUserId;
919 799 }
920 800
921 801 do_action('fluent_booking/before_creating_schedule', $bookingData, $postedData, $calendarEvent);
@@ -945,26 +825,52 @@
945 825 'booking_hash' => $booking->hash
946 826 ], $booking), 200);
947 827 }
948 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 +
949 841 public function ajaxGetAvailableDates()
950 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 +
951 847 $startBenchmark = microtime(true);
952 848
953 849 $request = $_REQUEST; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
954 850
955 - $eventId = (int)$request['event_id'];
851 + $eventId = (int)Arr::get($request, 'event_id');
956 852
957 - $rescheduling = Arr::get($request, 'rescheduling', 'no');
853 + $reschedulingHash = sanitize_text_field(Arr::get($request, 'rescheduling_hash', '')); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
958 854
959 - $calendarEvent = CalendarSlot::findOrfail($eventId);
855 + $calendarEvent = $eventId ? CalendarSlot::find($eventId) : null;
960 856
961 - if (!$calendarEvent || ($calendarEvent->status != 'active' && $rescheduling == 'no')) {
857 + if (!$calendarEvent) {
962 858 wp_send_json([
963 859 'message' => __('Sorry, the host is not accepting any new bookings at the moment.', 'fluent-booking')
964 860 ], 422);
965 861 }
966 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 +
967 873 $calendar = $calendarEvent->calendar;
968 874 $startDate = sanitize_text_field(Arr::get($request, 'start_date')); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
969 875
970 876 if (!$startDate) {
@@ -983,9 +889,9 @@
983 889
984 890 $duration = (int)$calendarEvent->getDuration(Arr::get($request, 'duration', null));
985 891
986 892 $timeSlotService = TimeSlotServiceHandler::initService($calendar, $calendarEvent);
987 -
893 +
988 894 if (is_wp_error($timeSlotService)) {
989 895 return TimeSlotServiceHandler::sendError($timeSlotService, $calendarEvent, $timeZone);
990 896 }
991 897
@@ -994,9 +900,9 @@
994 900 if (is_wp_error($availableSpots)) {
995 901 return TimeSlotServiceHandler::sendError($availableSpots, $calendarEvent, $timeZone);
996 902 }
997 903
998 - $availableSpots = array_filter($availableSpots);
904 + $availableSpots = array_filter((array) $availableSpots);
999 905 $availableSpots = apply_filters('fluent_booking/available_slots_for_view', $availableSpots, $calendarEvent, $calendar, $timeZone, $duration);
1000 906
1001 907 wp_send_json([
1002 908 'available_slots' => $availableSpots,
@@ -1007,8 +913,20 @@
1007 913 }
1008 914
1009 915 public function getCalendarEventVars(Calendar $calendar, CalendarSlot $calendarEvent)
1010 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 +
1011 929 $calendarEvent->description = wpautop($calendarEvent->description);
1012 930 $calendarEvent->location_icon_html = $calendarEvent->defaultLocationHtml();
1013 931 $formFields = BookingFieldService::getBookingFields($calendarEvent);
1014 932
@@ -1016,18 +934,19 @@
1016 934 'id' => $calendarEvent->id,
1017 935 'max_lookup_date' => $calendarEvent->getMaxLookUpDate(),
1018 936 'min_lookup_date' => $calendarEvent->getMinLookUpDate(),
1019 937 'min_bookable_date' => $calendarEvent->getMinBookableDateTime(),
938 + 'is_display_spots' => $calendarEvent->isDisplaySpots(),
1020 939 'duration' => $calendarEvent->getDefaultDuration(),
1021 940 'title' => $calendarEvent->title,
1022 - 'location_settings' => $calendarEvent->location_settings,
941 + 'location_settings' => LocationService::sanitizePublicLocationSettings($calendarEvent->location_settings),
1023 942 'location_icon_html' => $calendarEvent->location_icon_html,
1024 943 'description' => $calendarEvent->description,
1025 944 'pre_selects' => null,
1026 - 'settings' => $calendarEvent->settings,
945 + 'settings' => $this->sanitizePublicEventSettings($calendarEvent->settings),
1027 946 'type' => $calendarEvent->type,
1028 947 'event_type' => $calendarEvent->event_type,
1029 - 'time_format' => Arr::get(get_option('_fluent_booking_settings'), 'time_format', '12'),
948 + 'time_format' => $globalConfig['time_format'],
1030 949 ];
1031 950
1032 951 $author = $calendar->getAuthorProfile(true);
1033 952 $author['name'] = $calendar->title;
@@ -1040,12 +959,13 @@
1040 959 'Schedule_Meeting' => __('Schedule Meeting', 'fluent-booking'),
1041 960 'Continue_to_Payments' => __('Continue to Payments', 'fluent-booking'),
1042 961 'Confirm_Payment' => __('Confirm Payment', 'fluent-booking'),
1043 962 ],
1044 - 'date_formatter' => DateTimeHelper::getDateFormatter(true),
1045 - 'isRtl' => Helper::fluentbooking_is_rtl(),
1046 - 'duration_lookup' => Helper::getDurationLookup(),
1047 - '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']
1048 968 ];
1049 969
1050 970 $eventVars['form_fields'] = array_values($eventVars['form_fields']);
1051 971
@@ -1055,10 +975,41 @@
1055 975
1056 976 return apply_filters('fluent_booking/public_event_vars', $eventVars, $calendarEvent);
1057 977 }
1058 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 +
1059 1006 public function ajaxHandleCancelMeeting()
1060 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 +
1061 1012 $data = $_REQUEST; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
1062 1013
1063 1014 $meetingHash = Arr::get($data, 'meeting_hash');
1064 1015
@@ -1089,9 +1040,9 @@
1089 1040 $result = $meeting->cancelMeeting($message, 'guest', get_current_user_id());
1090 1041
1091 1042 if (is_wp_error($result)) {
1092 1043 if (!wp_doing_ajax()) {
1093 - wp_redirect($meeting->getConfirmationUrl());
1044 + wp_safe_redirect($meeting->getConfirmationUrl());
1094 1045 exit();
1095 1046 }
1096 1047
1097 1048 wp_send_json([
@@ -1104,9 +1055,9 @@
1104 1055 'message' => __('Meeting has been cancelled', 'fluent-booking')
1105 1056 ], 200);
1106 1057 }
1107 1058
1108 - wp_redirect($meeting->getConfirmationUrl());
1059 + wp_safe_redirect($meeting->getConfirmationUrl());
1109 1060 exit;
1110 1061 }
1111 1062
1112 1063 private static function sanitize_mapped_data($settings)