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 +338 -324 1.5.1 → 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,19 +126,26 @@
248 126 $calendarIds[] = $event->calendar_id;
249 127 if (!isset($calendarEvents[$event->calendar_id])) {
250 128 $calendarEvents[$event->calendar_id] = [];
251 129 }
252 - $event->durations = $event->getAvailableDurations();
253 - $event->description = $event->getDescription();
254 - $event->short_description = Helper::excerpt($event->description);
255 - $event->locations = $event->defaultLocationHtml();
130 + $event = CalendarEventService::processEvent($event);
256 131 $calendarEvents[$event->calendar_id][] = $event;
257 132 }
258 133
259 - $calendars = Calendar::query()->whereIn('id', $calendarIds)->get();
134 + $calendars = Calendar::query()->with('metas')->whereIn('id', $calendarIds)->get();
260 135
261 136 foreach ($calendars as $calendar) {
262 - $calendar->activeEvents = $calendarEvents[$calendar->id];
137 + $calendar->activeEvents = $calendarEvents[$calendar->id] ?? [];
138 + $eventOrder = $calendar->getMeta('event_order');
139 + if (!empty($eventOrder)) {
140 + $eventsArray = $calendar->activeEvents;
141 + usort($eventsArray, function($a, $b) use ($eventOrder) {
142 + $posA = array_search($a->id, $eventOrder);
143 + $posB = array_search($b->id, $eventOrder);
144 + return $posA - $posB;
145 + });
146 + $calendar->activeEvents = $eventsArray;
147 + }
263 148 }
264 149
265 150 return $this->renderTeamHosts($calendars, [
266 151 'title' => $atts['title'],
@@ -272,9 +157,9 @@
272 157
273 158 public function renderTeamHosts($calendars, $headerConfig = [])
274 159 {
275 160 $wrapperId = 'fcal_team_' . Helper::getNextIndex();
276 - 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);
277 162
278 163 $vars = [];
279 164 foreach ($calendars as $calendar) {
280 165 $hostHtml = (string)(string)\FluentBooking\App\App::getInstance('view')->make('landing.author_html', [
@@ -304,10 +189,9 @@
304 189 }
305 190
306 191 wp_localize_script('fluent-booking-team', $wrapperId, $vars);
307 192
308 - $assetUrl = App::getInstance('url.assets');
309 - 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);
310 194 $this->loadGlobalVars();
311 195
312 196 return App::make('view')->make('public.team_page', [
313 197 'hosts' => $calendars,
@@ -345,15 +229,29 @@
345 229 if (!$calendar) {
346 230 return '';
347 231 }
348 232
233 + $settings = LandingPageHelper::getSettings($calendar, 'public');
234 +
349 235 $calendarEventQuery = CalendarSlot::where('calendar_id', $calendar->id)
350 236 ->where('status', 'active');
351 -
237 +
238 + $enabledEvents = [];
239 + $isEnabledOnly = false;
240 + if ($settings['show_type'] != 'all') {
241 + $isEnabledOnly = true;
242 + $enabledEvents = $settings['enabled_slots'];
243 + }
244 +
352 245 if ($eventIds && $eventIds != 'all') {
353 - $calendarEventQuery->whereIn('id', $eventIds);
246 + $isEnabledOnly = true;
247 + $enabledEvents = !empty($enabledEvents) ? array_intersect($enabledEvents, $eventIds) : $eventIds;
354 248 }
355 249
250 + if (!empty($enabledEvents) || $isEnabledOnly) {
251 + $calendarEventQuery->whereIn('id', $enabledEvents);
252 + }
253 +
356 254 $calendarEvents = $calendarEventQuery->get();
357 255
358 256 if ($calendarEvents->isEmpty()) {
359 257 return '';
@@ -358,16 +256,10 @@
358 256 if ($calendarEvents->isEmpty()) {
359 257 return '';
360 258 }
361 259
362 - foreach ($calendarEvents as $event) {
363 - $event->public_url = $event->getPublicUrl();
364 - $event->durations = $event->getAvailableDurations();
365 - $event->description = $event->getDescription();
366 - $event->short_description = Helper::excerpt($event->description);
367 - $event->locations = $event->defaultLocationHtml();
368 - }
369 -
260 + $calendarEvents = CalendarEventService::processEvents($calendar, $calendarEvents);
261 +
370 262 $calendar->activeEvents = $calendarEvents;
371 263
372 264 return $this->renderCalendarBlock($calendar, [
373 265 'title' => $title,
@@ -379,10 +271,10 @@
379 271 }
380 272
381 273 public function renderCalendarBlock($calendar, $headerConfig = [])
382 274 {
383 - $wrapperId = 'fcal_team_' . Helper::getNextIndex();
384 - 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);
385 277
386 278 $calendarHtml = (string)(string)\FluentBooking\App\App::getInstance('view')->make('landing.author_html', [
387 279 'author' => $calendar->getAuthorProfile(),
388 280 'calendar' => $calendar,
@@ -409,10 +301,9 @@
409 301 }
410 302
411 303 wp_localize_script('fluent-booking-calendar', $wrapperId, $vars);
412 304
413 - $assetUrl = App::getInstance('url.assets');
414 - 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);
415 306 $this->loadGlobalVars();
416 307
417 308 return App::make('view')->make('public.calendar_page', [
418 309 'calendar' => $calendar,
@@ -419,9 +310,10 @@
419 310 'wrapper_id' => $wrapperId,
420 311 'logo' => Arr::get($headerConfig, 'logo', ''),
421 312 'title' => Arr::get($headerConfig, 'title', ''),
422 313 'description' => Arr::get($headerConfig, 'description', ''),
423 - 'wrapper_class' => Arr::get($headerConfig, 'wrapper_class', '')
314 + 'wrapper_class' => Arr::get($headerConfig, 'wrapper_class', ''),
315 + 'hide_info' => Arr::isTrue($headerConfig, 'hide_info')
424 316 ]);
425 317 }
426 318
427 319 public function handleBookingListsShortcode($atts, $content)
@@ -434,9 +326,9 @@
434 326 'calendar_ids' => 'all',
435 327 'no_bookings' => __('No bookings found', 'fluent-booking'),
436 328 'per_page' => 10
437 329 ], $atts);
438 -
330 +
439 331 $atts['title'] = sanitize_text_field($atts['title']);
440 332 $atts['filter'] = sanitize_text_field($atts['filter']);
441 333 $atts['pagination'] = sanitize_text_field($atts['pagination']);
442 334 $atts['no_bookings'] = sanitize_text_field($atts['no_bookings']);
@@ -441,15 +333,15 @@
441 333 $atts['pagination'] = sanitize_text_field($atts['pagination']);
442 334 $atts['no_bookings'] = sanitize_text_field($atts['no_bookings']);
443 335
444 336 $userData = get_userdata(get_current_user_id());
445 -
337 +
446 338 $userEmail = $userData ? $userData->user_email : null;
447 -
339 +
448 340 if (!$userEmail) {
449 341 return __('Please login to view your bookings', 'fluent-booking');
450 342 }
451 -
343 +
452 344 $data = $_REQUEST; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
453 345
454 346 $perPage = intval(Arr::get($data, 'booking_per_page', $atts['per_page']));
455 347 $currentPage = intval(Arr::get($data, 'booking_page', 1));
@@ -456,10 +348,10 @@
456 348 $bookingPeriod = sanitize_text_field(Arr::get($data, 'booking_period', $atts['period']));
457 349
458 350 $bookingQuery = Booking::query()->with('calendar_event')
459 351 ->where('email', $userEmail)
460 - ->orderBy('start_time', 'DESC')
461 - ->applyComputedStatus($bookingPeriod);
352 + ->applyComputedStatus($bookingPeriod)
353 + ->applyBookingOrderByStatus($bookingPeriod);
462 354
463 355 if ($atts['calendar_ids'] != 'all') {
464 356 $atts['calendar_ids'] = array_map('intval', explode(',', $atts['calendar_ids']));
465 357 $bookingQuery->whereIn('calendar_id', $atts['calendar_ids']);
@@ -475,9 +367,9 @@
475 367 $booking->booking_status_text = $booking->getBookingStatus();
476 368 $booking->payment_status_text = $booking->getPaymentStatus();
477 369
478 370 $booking->booking_date = DateTimeHelper::formatToLocale($booking->getAttendeeStartTime(), 'date');
479 - $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');
480 372 }
481 373
482 374 $currentPage = $bookings->currentPage();
483 375 $lastPage = $bookings->lastPage();
@@ -495,9 +387,9 @@
495 387 $periodOptions = Helper::getBookingPeriodOptions();
496 388
497 389 $pageOptions = apply_filters('fluent_booking/booking_per_page_options', [5, 10, 15, 20, 50, 100]);
498 390
499 - 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);
500 392
501 393 return App::make('view')->make('public.bookings', [
502 394 'bookings' => $bookings,
503 395 'attributes' => $atts,
@@ -515,13 +407,88 @@
515 407 if (!isset($_REQUEST['hash'])) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
516 408 return __('Booking hash is missing!', 'fluent-booking');
517 409 }
518 410
519 - $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
520 412
521 413 return apply_filters('fluent_booking/payment_receipt_html', '', $hash);
522 414 }
523 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 +
524 491 private function loadGlobalVars()
525 492 {
526 493 static $loaded;
527 494
@@ -584,113 +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 - 'Minutes' => __('Minutes', 'fluent-booking'),
595 - 'Enter Details' => __('Enter Details', 'fluent-booking'),
596 - 'Summary' => __('Summary', 'fluent-booking'),
597 - 'Payment Details' => __('Payment Details', 'fluent-booking'),
598 - 'Total Payment' => __('Total Payment', 'fluent-booking'),
599 - 'Payment Method' => __('Payment Method', 'fluent-booking'),
600 - 'Pay Now' => __('Pay Now', 'fluent-booking'),
601 - 'processing' => __('Processing', 'fluent-booking'),
602 - 'date_time_config' => [
603 - 'weekdays' => array(
604 - 'sunday' => _x('Sunday', 'calendar day full', 'fluent-booking'),
605 - 'monday' => _x('Monday', 'calendar day full', 'fluent-booking'),
606 - 'tuesday' => _x('Tuesday', 'calendar day full', 'fluent-booking'),
607 - 'wednesday' => _x('Wednesday', 'calendar day full', 'fluent-booking'),
608 - 'thursday' => _x('Thursday', 'calendar day full', 'fluent-booking'),
609 - 'friday' => _x('Friday', 'calendar day full', 'fluent-booking'),
610 - 'saturday' => _x('Saturday', 'calendar day full', 'fluent-booking'),
611 - ),
612 - 'months' => array(
613 - 'January' => _x('January', 'calendar month name full', 'fluent-booking'),
614 - 'February' => _x('February', 'calendar month name full', 'fluent-booking'),
615 - 'March' => _x('March', 'calendar month name full', 'fluent-booking'),
616 - 'April' => _x('April', 'calendar month name full', 'fluent-booking'),
617 - 'May' => _x('May', 'calendar month name full', 'fluent-booking'),
618 - 'June' => _x('June', 'calendar month name full', 'fluent-booking'),
619 - 'July' => _x('July', 'calendar month name full', 'fluent-booking'),
620 - 'August' => _x('August', 'calendar month name full', 'fluent-booking'),
621 - 'September' => _x('September', 'calendar month name full', 'fluent-booking'),
622 - 'October' => _x('October', 'calendar month name full', 'fluent-booking'),
623 - 'November' => _x('November', 'calendar month name full', 'fluent-booking'),
624 - 'December' => _x('December', 'calendar month name full', 'fluent-booking')
625 - ),
626 - 'weekdaysShort' => array(
627 - 'sun' => _x('Sun', 'calendar day short', 'fluent-booking'),
628 - 'mon' => _x('Mon', 'calendar day short', 'fluent-booking'),
629 - 'tue' => _x('Tue', 'calendar day short', 'fluent-booking'),
630 - 'wed' => _x('Wed', 'calendar day short', 'fluent-booking'),
631 - 'thu' => _x('Thu', 'calendar day short', 'fluent-booking'),
632 - 'fri' => _x('Fri', 'calendar day short', 'fluent-booking'),
633 - 'sat' => _x('Sat', 'calendar day short', 'fluent-booking')
634 - ),
635 - 'monthsShort' => array(
636 - 'jan' => _x('Jan', 'calendar month name short', 'fluent-booking'),
637 - 'feb' => _x('Feb', 'calendar month name short', 'fluent-booking'),
638 - 'mar' => _x('Mar', 'calendar month name short', 'fluent-booking'),
639 - 'apr' => _x('Apr', 'calendar month name short', 'fluent-booking'),
640 - 'may' => _x('May', 'calendar month name short', 'fluent-booking'),
641 - 'jun' => _x('Jun', 'calendar month name short', 'fluent-booking'),
642 - 'jul' => _x('Jul', 'calendar month name short', 'fluent-booking'),
643 - 'aug' => _x('Aug', 'calendar month name short', 'fluent-booking'),
644 - 'sep' => _x('Sep', 'calendar month name short', 'fluent-booking'),
645 - 'oct' => _x('Oct', 'calendar month name short', 'fluent-booking'),
646 - 'nov' => _x('Nov', 'calendar month name short', 'fluent-booking'),
647 - 'dec' => _x('Dec', 'calendar month name short', 'fluent-booking')
648 - ),
649 - 'numericSystem' => _x('0_1_2_3_4_5_6_7_8_9', 'calendar numeric system - Sequence must need to maintained', 'fluent-booking'),
650 - ],
651 - 'Country' => __('Country', 'fluent-booking'),
652 - '12h' => _x('12h', 'date time format switch', 'fluent-booking'),
653 - '24h' => _x('24h', 'date time format switch', 'fluent-booking'),
654 - 'spots left' => _x('spots left', 'for how many spots left for available booking', 'fluent-booking'),
655 - 'spots remaining' => _x('spots remaining', 'for how many spots remaining for available booking', 'fluent-booking'),
656 - 'Next' => _x('Next', 'Booking form spot selection', 'fluent-booking'),
657 - 'Select on the Next Step' => __('Select on the Next Step', 'fluent-booking'),
658 - 'location options' => __('location options', 'fluent-booking'),
659 - 'Your address' => __('Your address', 'fluent-booking'),
660 - 'Organizer Phone Number' => __('Organizer Phone Number', 'fluent-booking'),
661 - 'In Person (Attendee Address)' => __('In Person (Attendee Address)', 'fluent-booking'),
662 - 'In Person (Organizer Address)' => __('In Person (Organizer Address)', 'fluent-booking'),
663 - 'Attendee Phone Number' => __('Attendee Phone Number', 'fluent-booking'),
664 - 'Google Meet' => __('Google Meet', 'fluent-booking'),
665 - 'Zoom Meeting' => __('Zoom Meeting', 'fluent-booking'),
666 - 'Online Meeting' => __('Online Meeting', 'fluent-booking'),
667 - 'Phone Call' => __('Phone Call', 'fluent-booking'),
668 - 'Processing...' => __('Processing...', 'fluent-booking'),
669 - 'Loading Payment Processor...' => __('Loading Payment Processor...', 'fluent-booking'),
670 - 'PM' => __('PM', 'fluent-booking'),
671 - 'AM' => __('AM', 'fluent-booking'),
672 - 'Email' => __('Email', 'fluent-booking'),
673 - 'Date' => __('Date', 'fluent-booking'),
674 - 'Time' => __('Time', 'fluent-booking'),
675 - 'Add guests' => __('Add guests', 'fluent-booking'),
676 - 'Add another' => __('Add another', 'fluent-booking'),
677 - 'This field is required.' => __('This field is required.', 'fluent-booking'),
678 - 'No availability in' => __('No availability in', 'fluent-booking'),
679 - 'View next month' => __('View next month', 'fluent-booking'),
680 - 'View previous month' => __('View previous month', 'fluent-booking'),
681 - 'No_payment_method_description' => __('No activated payment method found. If you are an admin please check the event payment settings', 'fluent-booking'),
682 - 'Please fill up the required data' => __('Please fill up the required data', 'fluent-booking'),
683 - 'Please select a valid payment method' => __('Please select a valid payment method', 'fluent-booking'),
684 - 'Please Select' => __('Please Select', 'fluent-booking'),
685 - 'Something is wrong!' => __('Something is wrong!', 'fluent-booking'),
686 - 'Requires Confirmation' => __('Requires Confirmation', 'fluent-booking'),
687 - ],
688 - '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()
689 565 ];
690 566
691 567 if (isset($_SERVER['HTTP_CF_IPCOUNTRY'])) {
692 - $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
693 569 } else {
694 570 $data['user_country'] = Arr::get($globalSettings, 'administration.default_country', '');
695 571 }
696 572
@@ -698,8 +574,12 @@
698 574 }
699 575
700 576 public function ajaxScheduleMeeting()
701 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 +
702 582 $app = App::getInstance();
703 583
704 584 $postedData = $_REQUEST; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
705 585
@@ -710,8 +590,9 @@
710 590 $calendarEvent = CalendarSlot::find($eventId);
711 591
712 592 if (!$calendarEvent || ($calendarEvent->status != 'active' && !$isRescheduling)) {
713 593 wp_send_json([
594 + 'message' => __('Sorry, the host is not accepting any new bookings at the moment.', 'fluent-booking')
714 595 ], 422);
715 596 }
716 597
717 598 do_action('fluent_booking/starting_scheduling_ajax', $postedData);
@@ -716,12 +597,17 @@
716 597
717 598 do_action('fluent_booking/starting_scheduling_ajax', $postedData);
718 599
719 600 $rules = [
720 - 'name' => 'required',
721 - 'email' => 'required|email',
722 - 'timezone' => 'required',
723 - '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',
724 610 ];
725 611
726 612 $messages = [
727 613 'name.required' => __('Please enter your name', 'fluent-booking'),
@@ -731,9 +617,9 @@
731 617 'start_date.required' => __('Please select a date and time', 'fluent-booking')
732 618 ];
733 619
734 620 if ($calendarEvent->isPhoneRequired()) {
735 - $rules['phone_number'] = 'required';
621 + $rules['phone_number'] = ['required', $this->validPhoneNumberRule()];
736 622 $messages['phone_number.required'] = __('Please provide your phone number', 'fluent-booking');
737 623 } else if ($calendarEvent->isAddressRequired()) {
738 624 $rules['address'] = 'required';
739 625 $messages['address.required'] = __('Please provide your Address', 'fluent-booking');
@@ -744,27 +630,32 @@
744 630 $selectedLocation = LocationService::getLocationDetails($calendarEvent, Arr::get($postedData, 'location_config', []), $postedData);
745 631 $selectedLocationDriver = Arr::get($selectedLocation, 'type');
746 632 // is user input required
747 633 if (in_array($selectedLocationDriver, ['in_person_guest', 'phone_guest'])) {
748 - $rules['location_config.user_location_input'] = 'required';
749 634 if ($selectedLocationDriver == 'in_person_guest') {
635 + $rules['location_config.user_location_input'] = 'required';
750 636 $messages['location_config.user_location_input.required'] = __('Please provide your address', 'fluent-booking');
751 637 } else {
638 + $rules['location_config.user_location_input'] = ['required', $this->validPhoneNumberRule()];
752 639 $messages['location_config.user_location_input.required'] = __('Please provide your phone number', 'fluent-booking');
753 640 }
754 641 }
755 642 }
756 643
757 - $duration = (int)$calendarEvent->getDuration(Arr::get($postedData, 'duration', null));
644 + $duration = (int)$calendarEvent->getDuration(Arr::get($postedData, 'duration', null));
758 645
759 - if ($calendarEvent->isPaymentEnabled($duration)) {
760 - $rules['payment_method'] = 'required';
761 - $messages['payment_method.required'] = __('Please select a valid payment method', 'fluent-booking');
646 + if ($additionalGuests = Arr::get($postedData, 'guests', [])) {
647 + if ($calendarEvent->isMultiGuestEvent()) {
648 + $additionalGuests = $this->sanitize_mapped_data($additionalGuests);
649 + $additionalGuests = array_values(array_filter($additionalGuests, function ($guest) {
650 + return Arr::get($guest, 'name') && Arr::get($guest, 'email');
651 + }));
652 + } else {
653 + $additionalGuests = array_filter(array_map('sanitize_email', $additionalGuests));
654 + }
762 655 }
763 656
764 - if ($additionalGuests = Arr::get($postedData, 'guests', [])) {
765 - $postedData['guests'] = array_filter(array_map('sanitize_email', $additionalGuests));
766 - }
657 + $postedData['guests'] = $additionalGuests;
767 658
768 659 $requiredFields = array_filter($calendarEvent->getMeta('booking_fields', []), function ($field) {
769 660 return Arr::isTrue($field, 'required') && Arr::isTrue($field, 'enabled') && (Arr::get($field, 'name') == 'message' || Arr::get($field, 'name') == 'guests');
770 661 });
@@ -782,17 +673,17 @@
782 673 ], $postedData, $calendarEvent);
783 674
784 675 $validator = $app->validator->make($postedData, $validationConfig['rules'], $validationConfig['messages']);
785 676 if ($validator->validate()->fails()) {
677 + $errorMessage = $validator->firstError() ?: __('Please fill up the required data', 'fluent-booking');
786 678 wp_send_json([
787 - 'message' => __('Please fill up the required data', 'fluent-booking'),
679 + 'message' => $errorMessage,
788 680 'errors' => $validator->errors()
789 681 ], 422);
790 - return;
791 682 }
792 683
793 684 $customFieldsData = BookingFieldService::getCustomFieldsData($postedData, $calendarEvent);
794 - $customFieldsData = apply_filters('fluent_booking/schedule_custom_field_data', $customFieldsData, $customFieldsData, $calendarEvent);
685 + $customFieldsData = apply_filters('fluent_booking/schedule_custom_field_data', $customFieldsData, $calendarEvent);
795 686
796 687 if (is_wp_error($customFieldsData)) {
797 688 wp_send_json([
798 689 'message' => $customFieldsData->get_error_message(),
@@ -797,37 +688,65 @@
797 688 wp_send_json([
798 689 'message' => $customFieldsData->get_error_message(),
799 690 'errors' => $customFieldsData->get_error_data()
800 691 ], 422);
801 - return;
802 692 }
803 693
804 - $startDate = sanitize_text_field(Arr::get($postedData, 'start_date'));
805 - $timezone = sanitize_text_field(Arr::get($postedData, 'timezone', 'UTC'));
694 + $validateDateFields = BookingFieldService::validateDateFields($customFieldsData, $calendarEvent);
806 695
807 - $startDateTime = DateTimeHelper::convertToUtc($startDate, $timezone);
808 - $endDateTime = gmdate('Y-m-d H:i:s', strtotime($startDateTime) + ($duration * 60)); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
696 + if (is_wp_error($validateDateFields)) {
697 + wp_send_json([
698 + 'message' => $validateDateFields->get_error_message(),
699 + ], 422);
700 + }
809 701
810 - $bookingData = [
702 + $startDate = Arr::get($postedData, 'start_date');
703 + $timezone = sanitize_text_field(Arr::get($postedData, 'timezone', 'UTC'));
704 +
705 + if (is_array($startDate)) {
706 + $startDateTime = array_slice(
707 + array_map(function($date) use ($timezone) {
708 + return DateTimeHelper::convertToUtc(sanitize_text_field($date), $timezone);
709 + }, $startDate), 0, $calendarEvent->multiBookingLimit()
710 + );
711 + $endDateTime = array_map(function($date) use ($duration) {
712 + return gmdate('Y-m-d H:i:s', strtotime($date) + ($duration * 60)); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
713 + }, $startDateTime);
714 + }
715 +
716 + if (is_string($startDate)) {
717 + $startDate = sanitize_text_field($startDate);
718 + $startDateTime = DateTimeHelper::convertToUtc($startDate, $timezone);
719 + $endDateTime = gmdate('Y-m-d H:i:s', strtotime($startDateTime) + ($duration * 60)); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
720 + }
721 +
722 + $bookingData = apply_filters('fluent_booking/initialize_booking_data', [
811 723 'person_time_zone' => sanitize_text_field($timezone),
812 724 'start_time' => $startDateTime,
725 + 'end_time' => $endDateTime,
813 726 'name' => sanitize_text_field($postedData['name']),
814 727 'email' => sanitize_email($postedData['email']),
815 728 'message' => sanitize_textarea_field(wp_unslash(Arr::get($postedData, 'message', ''))),
816 - 'phone' => sanitize_textarea_field(Arr::get($postedData, 'phone_number', '')),
729 + 'phone' => sanitize_text_field(Arr::get($postedData, 'phone_number', '')),
817 730 'address' => sanitize_textarea_field(Arr::get($postedData, 'address', '')),
818 731 'ip_address' => Helper::getIp(),
819 732 'status' => 'scheduled',
820 733 'source' => 'web',
821 734 'event_type' => $calendarEvent->event_type,
822 - 'slot_minutes' => $duration
823 - ];
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);
824 742
825 - if ($calendarEvent->isConfirmationRequired($startDateTime)) {
743 + if ($calendarEvent->isConfirmationRequired($bookingData['start_time'])) {
826 744 $bookingData['status'] = 'pending';
827 745 }
828 746
829 - $selectedLocation = LocationService::getLocationDetails($calendarEvent, Arr::get($postedData, 'location_config', []), $postedData);
747 + $locationConfig = Arr::get($postedData, 'location_config', []);
748 + $selectedLocation = LocationService::getLocationDetails($calendarEvent, $locationConfig, $postedData);
830 749 if ($selectedLocation['type'] == 'phone_guest') {
831 750 $bookingData['phone'] = $selectedLocation['description'];
832 751 }
833 752
@@ -836,16 +755,18 @@
836 755 if ($sourceUrl = Arr::get($postedData, 'source_url', '')) {
837 756 $bookingData['source_url'] = sanitize_url($sourceUrl);
838 757 }
839 758
759 + if (!empty($postedData['coupon_codes'])) {
760 + $bookingData['coupon_codes'] = array_map('sanitize_text_field', array_unique($postedData['coupon_codes']));
761 + }
762 +
840 763 if (!empty($postedData['payment_method'])) {
841 - $customFieldsData['payment_method'] = $postedData['payment_method'];
764 + $customFieldsData['payment_method'] = sanitize_text_field($postedData['payment_method']);
842 765 }
843 766
844 - if ($additionalGuests) {
845 - $guestField = BookingFieldService::getBookingFieldByName($calendarEvent, 'guests');
846 - $guestLimit = Arr::get($guestField, 'limit', 10);
847 - $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);
848 769 }
849 770
850 771 $timeSlotService = TimeSlotServiceHandler::initService($calendarEvent->calendar, $calendarEvent);
851 772
@@ -852,17 +773,29 @@
852 773 if (is_wp_error($timeSlotService)) {
853 774 return TimeSlotServiceHandler::sendError($timeSlotService, $calendarEvent, $timezone);
854 775 }
855 776
856 - $isSpotAvailable = $timeSlotService->isSpotAvailable($startDateTime, $endDateTime, $duration);
777 + $isSlotLocked = Helper::lockRoundRobinSlot($calendarEvent, $bookingData['start_time'], $bookingData['end_time']);
857 778
858 - if (!$isSpotAvailable) {
779 + $availableSpot = $isSlotLocked ? $timeSlotService->isSpotAvailable($bookingData['start_time'], $bookingData['end_time'], $duration) : false;
780 +
781 + if (!$availableSpot) {
859 782 wp_send_json([
860 783 'message' => __('This selected time slot is not available. Maybe someone booked the spot just a few seconds ago.', 'fluent-booking')
861 784 ], 422);
862 785 }
863 786
864 - 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()) {
865 798 $bookingData['host_user_id'] = $timeSlotService->hostUserId;
866 799 }
867 800
868 801 do_action('fluent_booking/before_creating_schedule', $bookingData, $postedData, $calendarEvent);
@@ -892,26 +825,52 @@
892 825 'booking_hash' => $booking->hash
893 826 ], $booking), 200);
894 827 }
895 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 +
896 841 public function ajaxGetAvailableDates()
897 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 +
898 847 $startBenchmark = microtime(true);
899 848
900 849 $request = $_REQUEST; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
901 850
902 - $eventId = (int)$request['event_id'];
851 + $eventId = (int)Arr::get($request, 'event_id');
903 852
904 - $rescheduling = Arr::get($request, 'rescheduling', 'no');
853 + $reschedulingHash = sanitize_text_field(Arr::get($request, 'rescheduling_hash', '')); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
905 854
906 - $calendarEvent = CalendarSlot::findOrfail($eventId);
855 + $calendarEvent = $eventId ? CalendarSlot::find($eventId) : null;
907 856
908 - if (!$calendarEvent || ($calendarEvent->status != 'active' && $rescheduling == 'no')) {
857 + if (!$calendarEvent) {
909 858 wp_send_json([
910 859 'message' => __('Sorry, the host is not accepting any new bookings at the moment.', 'fluent-booking')
911 860 ], 422);
912 861 }
913 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 +
914 873 $calendar = $calendarEvent->calendar;
915 874 $startDate = sanitize_text_field(Arr::get($request, 'start_date')); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
916 875
917 876 if (!$startDate) {
@@ -930,9 +889,9 @@
930 889
931 890 $duration = (int)$calendarEvent->getDuration(Arr::get($request, 'duration', null));
932 891
933 892 $timeSlotService = TimeSlotServiceHandler::initService($calendar, $calendarEvent);
934 -
893 +
935 894 if (is_wp_error($timeSlotService)) {
936 895 return TimeSlotServiceHandler::sendError($timeSlotService, $calendarEvent, $timeZone);
937 896 }
938 897
@@ -941,9 +900,9 @@
941 900 if (is_wp_error($availableSpots)) {
942 901 return TimeSlotServiceHandler::sendError($availableSpots, $calendarEvent, $timeZone);
943 902 }
944 903
945 - $availableSpots = array_filter($availableSpots);
904 + $availableSpots = array_filter((array) $availableSpots);
946 905 $availableSpots = apply_filters('fluent_booking/available_slots_for_view', $availableSpots, $calendarEvent, $calendar, $timeZone, $duration);
947 906
948 907 wp_send_json([
949 908 'available_slots' => $availableSpots,
@@ -954,8 +913,20 @@
954 913 }
955 914
956 915 public function getCalendarEventVars(Calendar $calendar, CalendarSlot $calendarEvent)
957 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 +
958 929 $calendarEvent->description = wpautop($calendarEvent->description);
959 930 $calendarEvent->location_icon_html = $calendarEvent->defaultLocationHtml();
960 931 $formFields = BookingFieldService::getBookingFields($calendarEvent);
961 932
@@ -963,18 +934,19 @@
963 934 'id' => $calendarEvent->id,
964 935 'max_lookup_date' => $calendarEvent->getMaxLookUpDate(),
965 936 'min_lookup_date' => $calendarEvent->getMinLookUpDate(),
966 937 'min_bookable_date' => $calendarEvent->getMinBookableDateTime(),
938 + 'is_display_spots' => $calendarEvent->isDisplaySpots(),
967 939 'duration' => $calendarEvent->getDefaultDuration(),
968 940 'title' => $calendarEvent->title,
969 - 'location_settings' => $calendarEvent->location_settings,
941 + 'location_settings' => LocationService::sanitizePublicLocationSettings($calendarEvent->location_settings),
970 942 'location_icon_html' => $calendarEvent->location_icon_html,
971 943 'description' => $calendarEvent->description,
972 944 'pre_selects' => null,
973 - 'settings' => $calendarEvent->settings,
945 + 'settings' => $this->sanitizePublicEventSettings($calendarEvent->settings),
974 946 'type' => $calendarEvent->type,
975 947 'event_type' => $calendarEvent->event_type,
976 - 'time_format' => Arr::get(get_option('_fluent_booking_settings'), 'time_format', '12'),
948 + 'time_format' => $globalConfig['time_format'],
977 949 ];
978 950
979 951 $author = $calendar->getAuthorProfile(true);
980 952 $author['name'] = $calendar->title;
@@ -987,12 +959,13 @@
987 959 'Schedule_Meeting' => __('Schedule Meeting', 'fluent-booking'),
988 960 'Continue_to_Payments' => __('Continue to Payments', 'fluent-booking'),
989 961 'Confirm_Payment' => __('Confirm Payment', 'fluent-booking'),
990 962 ],
991 - 'date_formatter' => DateTimeHelper::getDateFormatter(true),
992 - 'isRtl' => Helper::fluentbooking_is_rtl(),
993 - 'duration_lookup' => Helper::getDurationLookup(),
994 - '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']
995 968 ];
996 969
997 970 $eventVars['form_fields'] = array_values($eventVars['form_fields']);
998 971
@@ -1002,10 +975,41 @@
1002 975
1003 976 return apply_filters('fluent_booking/public_event_vars', $eventVars, $calendarEvent);
1004 977 }
1005 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 +
1006 1006 public function ajaxHandleCancelMeeting()
1007 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 +
1008 1012 $data = $_REQUEST; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
1009 1013
1010 1014 $meetingHash = Arr::get($data, 'meeting_hash');
1011 1015
@@ -1036,9 +1040,9 @@
1036 1040 $result = $meeting->cancelMeeting($message, 'guest', get_current_user_id());
1037 1041
1038 1042 if (is_wp_error($result)) {
1039 1043 if (!wp_doing_ajax()) {
1040 - wp_redirect($meeting->getConfirmationUrl());
1044 + wp_safe_redirect($meeting->getConfirmationUrl());
1041 1045 exit();
1042 1046 }
1043 1047
1044 1048 wp_send_json([
@@ -1051,8 +1055,18 @@
1051 1055 'message' => __('Meeting has been cancelled', 'fluent-booking')
1052 1056 ], 200);
1053 1057 }
1054 1058
1055 - wp_redirect($meeting->getConfirmationUrl());
1059 + wp_safe_redirect($meeting->getConfirmationUrl());
1056 1060 exit;
1061 + }
1062 +
1063 + private static function sanitize_mapped_data($settings)
1064 + {
1065 + $sanitizerMap = [
1066 + 'name' => 'sanitize_text_field',
1067 + 'email' => 'sanitize_email',
1068 + ];
1069 +
1070 + return Helper::fcal_backend_sanitizer($settings, $sanitizerMap);
1057 1071 }
1058 1072 }