PluginProbe
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution / 1.5.10
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution v1.5.10
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 1.7.2 All 33 releases
fluent-booking / app / Hooks / Handlers / FrontEndHandler.php

FrontEndHandler.php in Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution 1.5.10, at app/Hooks/Handlers/FrontEndHandler.php

1,059 lines 47.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentBooking\App\Hooks\Handlers;
4
5 use FluentBooking\App\App;
6 use FluentBooking\App\Models\Booking;
7 use FluentBooking\App\Models\Calendar;
8 use FluentBooking\App\Models\CalendarSlot;
9 use FluentBooking\App\Services\BookingFieldService;
10 use FluentBooking\App\Services\BookingService;
11 use FluentBooking\App\Services\DateTimeHelper;
12 use FluentBooking\App\Services\Helper;
13 use FluentBooking\App\Services\LandingPage\LandingPageHandler;
14 use FluentBooking\App\Hooks\Handlers\TimeSlotServiceHandler;
15 use FluentBooking\App\Services\LocationService;
16 use FluentBooking\App\Services\TimeSlotService;
17 use FluentBooking\App\Services\PermissionManager;
18 use FluentBooking\Framework\Support\Arr;
19
20 class FrontEndHandler
21 {
22 public function register()
23 {
24 add_shortcode('fluent_booking', [$this, 'handleBookingShortcode']);
25
26 add_shortcode('fluent_booking_team', [$this, 'handleTeamShortcode']);
27
28 add_shortcode('fluent_booking_calendar', [$this, 'handleCalendarShortcode']);
29
30 add_shortcode('fluent_booking_lists', [$this, 'handleBookingListsShortcode']);
31
32 add_shortcode('fluent_booking_receipt', [$this, 'handleReceiptShortcode']);
33
34 add_action('wp_ajax_fluent_cal_schedule_meeting', [$this, 'ajaxScheduleMeeting']);
35 add_action('wp_ajax_nopriv_fluent_cal_schedule_meeting', [$this, 'ajaxScheduleMeeting']);
36
37 add_action('wp_ajax_fcal_cancel_meeting', [$this, 'ajaxHandleCancelMeeting']);
38 add_action('wp_ajax_nopriv_fcal_cancel_meeting', [$this, 'ajaxHandleCancelMeeting']);
39
40 add_action('wp_ajax_fluent_cal_get_available_dates', [$this, 'ajaxGetAvailableDates']);
41 add_action('wp_ajax_nopriv_fluent_cal_get_available_dates', [$this, 'ajaxGetAvailableDates']);
42
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 });
162 }
163
164 public function handleBookingShortcode($atts, $content)
165 {
166 $atts = shortcode_atts([
167 'id' => 0,
168 'disable_author' => 'no',
169 'theme' => 'light'
170 ], $atts);
171
172 if (!$atts['id']) {
173 return '';
174 }
175
176 $calendarEvent = CalendarSlot::query()->find($atts['id']);
177 if (!$calendarEvent) {
178 return '';
179 }
180
181 $calendar = $calendarEvent->calendar;
182 if (!$calendar) {
183 return __('Calendar not found', 'fluent-booking');
184 }
185
186 $assetUrl = App::getInstance('url.assets');
187
188 $localizeData = $this->getCalendarEventVars($calendar, $calendarEvent);
189 $localizeData['disable_author'] = $atts['disable_author'] == 'yes';
190 $localizeData['theme'] = $atts['theme'];
191
192 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 });
204 }
205
206 wp_enqueue_script('fluent-booking-public', $assetUrl . 'public/js/app.js', [], FLUENT_BOOKING_ASSETS_VERSION, true);
207
208 $this->loadGlobalVars();
209 wp_localize_script(
210 'fluent-booking-public',
211 'fcal_public_vars_' . $calendar->id . '_' . $calendarEvent->id,
212 $localizeData,
213 );
214
215 return App::make('view')->make('public.calendar', [
216 'calenderEvent' => $calendarEvent,
217 'theme' => $atts['theme']
218 ]);
219 }
220
221 public function handleTeamShortcode($atts, $content)
222 {
223 $atts = shortcode_atts([
224 'event_ids' => '',
225 'title' => '',
226 'description' => '',
227 'logo_url' => ''
228 ], $atts);
229
230 if (!$atts['event_ids']) {
231 return '';
232 }
233
234 $eventIds = array_filter(array_map('intval', explode(',', $atts['event_ids'])));
235
236 if (empty($eventIds)) {
237 return '';
238 }
239
240 $events = CalendarSlot::query()->whereIn('id', $eventIds)
241 ->where('status', 'active')
242 ->get();
243
244 $calendarIds = [];
245 $calendarEvents = [];
246
247 foreach ($events as $event) {
248 $calendarIds[] = $event->calendar_id;
249 if (!isset($calendarEvents[$event->calendar_id])) {
250 $calendarEvents[$event->calendar_id] = [];
251 }
252 $event->durations = $event->getAvailableDurations();
253 $event->description = $event->getDescription();
254 $event->short_description = Helper::excerpt($event->description);
255 $event->locations = $event->defaultLocationHtml();
256 $calendarEvents[$event->calendar_id][] = $event;
257 }
258
259 $calendars = Calendar::query()->whereIn('id', $calendarIds)->get();
260
261 foreach ($calendars as $calendar) {
262 $calendar->activeEvents = $calendarEvents[$calendar->id];
263 }
264
265 return $this->renderTeamHosts($calendars, [
266 'title' => $atts['title'],
267 'description' => $atts['description'],
268 'logo' => $atts['logo_url'],
269 'wrapper_class' => ''
270 ]);
271 }
272
273 public function renderTeamHosts($calendars, $headerConfig = [])
274 {
275 $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);
277
278 $vars = [];
279 foreach ($calendars as $calendar) {
280 $hostHtml = (string)(string)\FluentBooking\App\App::getInstance('view')->make('landing.author_html', [
281 'author' => $calendar->getAuthorProfile(),
282 'calendar' => $calendar,
283 'events' => $calendar->activeEvents
284 ]);
285
286 $hostHtml .= '<div onclick="fcalBackToTeam(this)" class="fcal_back_btn_team"><svg height="20px" version="1.1" viewBox="0 0 512 512" width="512px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><polygon points="352,128.4 319.7,96 160,256 160,256 160,256 319.7,416 352,383.6 224.7,256 "></polygon></svg> <span>' . __('Back to team', 'fluent-booking') . '</span></div>';
287
288 $eventCount = count($calendar->activeEvents);
289
290 $vars['fcal_host_' . $calendar->id] = [
291 'host_html' => $hostHtml,
292 'event_count' => $eventCount,
293 'target_event_id' => ($eventCount == 1) ? $calendar->activeEvents[0]->id : 0
294 ];
295
296 foreach ($calendar->activeEvents as $event) {
297 $itemVars = $this->getCalendarEventVars($event->calendar, $event);
298 $extraJs = (new LandingPageHandler())->getEventLandingExtraJsFiles($itemVars['form_fields'], $event);
299 if ($extraJs) {
300 $itemVars['lazy_js_files'] = $extraJs;
301 }
302 wp_localize_script('fluent-booking-team', 'fcal_public_vars_' . $event->calendar_id . '_' . $event->id, $itemVars);
303 }
304 }
305
306 wp_localize_script('fluent-booking-team', $wrapperId, $vars);
307
308 $assetUrl = App::getInstance('url.assets');
309 wp_enqueue_script('fluent-booking-public', $assetUrl . 'public/js/app.js', [], FLUENT_BOOKING_ASSETS_VERSION, true);
310 $this->loadGlobalVars();
311
312 return App::make('view')->make('public.team_page', [
313 'hosts' => $calendars,
314 'wrapper_id' => $wrapperId,
315 'logo' => Arr::get($headerConfig, 'logo', ''),
316 'title' => Arr::get($headerConfig, 'title', ''),
317 'description' => Arr::get($headerConfig, 'description', ''),
318 'wrapper_class' => Arr::get($headerConfig, 'wrapper_class', '')
319 ]);
320 }
321
322 public function handleCalendarShortcode($atts, $content)
323 {
324 $atts = shortcode_atts([
325 'calendar_id' => '',
326 'event_ids' => '',
327 'title' => '',
328 'description' => '',
329 'logo' => '',
330 'hide_info' => false
331 ], $atts);
332
333 $calendarId = intval($atts['calendar_id']);
334 $title = sanitize_text_field($atts['title']);
335 $description = sanitize_text_field($atts['description']);
336 $logo = sanitize_text_field($atts['logo']);
337 $hideInfo = $atts['hide_info'] ? true : false;
338 $eventIds = array_filter(array_map('intval', explode(',', $atts['event_ids'])));
339
340 if (!$calendarId) {
341 return '';
342 }
343
344 $calendar = Calendar::find($calendarId);
345 if (!$calendar) {
346 return '';
347 }
348
349 $calendarEventQuery = CalendarSlot::where('calendar_id', $calendar->id)
350 ->where('status', 'active');
351
352 if ($eventIds && $eventIds != 'all') {
353 $calendarEventQuery->whereIn('id', $eventIds);
354 }
355
356 $calendarEvents = $calendarEventQuery->get();
357
358 if ($calendarEvents->isEmpty()) {
359 return '';
360 }
361
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
370 $calendar->activeEvents = $calendarEvents;
371
372 return $this->renderCalendarBlock($calendar, [
373 'title' => $title,
374 'description' => $description,
375 'logo' => $logo,
376 'hide_info' => $hideInfo,
377 'wrapper_class' => '',
378 ]);
379 }
380
381 public function renderCalendarBlock($calendar, $headerConfig = [])
382 {
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);
385
386 $calendarHtml = (string)(string)\FluentBooking\App\App::getInstance('view')->make('landing.author_html', [
387 'author' => $calendar->getAuthorProfile(),
388 'calendar' => $calendar,
389 'events' => $calendar->activeEvents,
390 'hideInfo' => Arr::isTrue($headerConfig, 'hide_info'),
391 'block' => true
392 ]);
393
394 $eventCount = count($calendar->activeEvents);
395
396 $vars['fcal_host_calendar' ] = [
397 'calendar_html' => $calendarHtml,
398 'event_count' => $eventCount,
399 'target_event_id' => ($eventCount == 1) ? $calendar->activeEvents[0]->id : 0
400 ];
401
402 foreach ($calendar->activeEvents as $event) {
403 $itemVars = $this->getCalendarEventVars($event->calendar, $event);
404 $extraJs = (new LandingPageHandler())->getEventLandingExtraJsFiles($itemVars['form_fields'], $event);
405 if ($extraJs) {
406 $itemVars['lazy_js_files'] = $extraJs;
407 }
408 wp_localize_script('fluent-booking-calendar', 'fcal_public_vars_' . $event->calendar_id . '_' . $event->id, $itemVars);
409 }
410
411 wp_localize_script('fluent-booking-calendar', $wrapperId, $vars);
412
413 $assetUrl = App::getInstance('url.assets');
414 wp_enqueue_script('fluent-booking-public', $assetUrl . 'public/js/app.js', [], FLUENT_BOOKING_ASSETS_VERSION, true);
415 $this->loadGlobalVars();
416
417 return App::make('view')->make('public.calendar_page', [
418 'calendar' => $calendar,
419 'wrapper_id' => $wrapperId,
420 'logo' => Arr::get($headerConfig, 'logo', ''),
421 'title' => Arr::get($headerConfig, 'title', ''),
422 'description' => Arr::get($headerConfig, 'description', ''),
423 'wrapper_class' => Arr::get($headerConfig, 'wrapper_class', '')
424 ]);
425 }
426
427 public function handleBookingListsShortcode($atts, $content)
428 {
429 $atts = shortcode_atts([
430 'title' => __('My Bookings', 'fluent-booking'),
431 'filter' => 'show',
432 'pagination' => 'show',
433 'period' => 'all',
434 'calendar_ids' => 'all',
435 'no_bookings' => __('No bookings found', 'fluent-booking'),
436 'per_page' => 10
437 ], $atts);
438
439 $atts['title'] = sanitize_text_field($atts['title']);
440 $atts['filter'] = sanitize_text_field($atts['filter']);
441 $atts['pagination'] = sanitize_text_field($atts['pagination']);
442 $atts['no_bookings'] = sanitize_text_field($atts['no_bookings']);
443
444 $userData = get_userdata(get_current_user_id());
445
446 $userEmail = $userData ? $userData->user_email : null;
447
448 if (!$userEmail) {
449 return __('Please login to view your bookings', 'fluent-booking');
450 }
451
452 $data = $_REQUEST; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
453
454 $perPage = intval(Arr::get($data, 'booking_per_page', $atts['per_page']));
455 $currentPage = intval(Arr::get($data, 'booking_page', 1));
456 $bookingPeriod = sanitize_text_field(Arr::get($data, 'booking_period', $atts['period']));
457
458 $bookingQuery = Booking::query()->with('calendar_event')
459 ->where('email', $userEmail)
460 ->orderBy('start_time', 'DESC')
461 ->applyComputedStatus($bookingPeriod);
462
463 if ($atts['calendar_ids'] != 'all') {
464 $atts['calendar_ids'] = array_map('intval', explode(',', $atts['calendar_ids']));
465 $bookingQuery->whereIn('calendar_id', $atts['calendar_ids']);
466 }
467
468 $bookings = $bookingQuery->paginate($perPage, ['*'], 'booking_page', $currentPage)
469 ->appends(['booking_page' => $currentPage])
470 ->withQueryString();
471
472 foreach ($bookings as &$booking) {
473 $booking->author_name = $booking->getHostDetails(false)['name'];
474 $booking->happening_status = $booking->getOngoingStatus();
475 $booking->booking_status_text = $booking->getBookingStatus();
476 $booking->payment_status_text = $booking->getPaymentStatus();
477
478 $booking->booking_date = DateTimeHelper::formatToLocale($booking->getAttendeeStartTime(), 'date');
479 $booking->booking_time = DateTimeHelper::formatToLocale($booking->getAttendeeEndTime(), 'time') . ' - ' . DateTimeHelper::formatToLocale($booking->getAttendeeEndTime(), 'time');
480 }
481
482 $currentPage = $bookings->currentPage();
483 $lastPage = $bookings->lastPage();
484 $startPage = max(1, $currentPage - 2);
485 $endPage = min($lastPage, $currentPage + 2);
486
487 // Adjust if near the beginning or the end
488 if ($currentPage < 3) {
489 $endPage = min($lastPage, 5);
490 }
491 if ($currentPage > $lastPage - 2) {
492 $startPage = max(1, $lastPage - 4);
493 }
494
495 $periodOptions = Helper::getBookingPeriodOptions();
496
497 $pageOptions = apply_filters('fluent_booking/booking_per_page_options', [5, 10, 15, 20, 50, 100]);
498
499 wp_enqueue_script('fluent-booking-list', App::getInstance('url.assets') . 'public/js/bookings.js', [], FLUENT_BOOKING_ASSETS_VERSION, true);
500
501 return App::make('view')->make('public.bookings', [
502 'bookings' => $bookings,
503 'attributes' => $atts,
504 'per_page' => $perPage,
505 'start_page' => $startPage,
506 'end_page' => $endPage,
507 'booking_period' => $bookingPeriod,
508 'page_options' => $pageOptions,
509 'period_options' => $periodOptions
510 ]);
511 }
512
513 public function handleReceiptShortcode($atts, $content)
514 {
515 if (!isset($_REQUEST['hash'])) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
516 return __('Booking hash is missing!', 'fluent-booking');
517 }
518
519 $hash = sanitize_text_field($_REQUEST['hash']); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
520
521 return apply_filters('fluent_booking/payment_receipt_html', '', $hash);
522 }
523
524 private function loadGlobalVars()
525 {
526 static $loaded;
527
528 if ($loaded) {
529 return;
530 }
531
532 $loaded = true;
533
534 wp_localize_script('fluent-booking-public', 'fluentCalendarPublicVars', $this->getGlobalVars());
535 }
536
537 public function getGlobalVars()
538 {
539 $currentPerson = [
540 'name' => '',
541 'email' => ''
542 ];
543
544 if (is_user_logged_in()) {
545 $currentUser = wp_get_current_user();
546 $name = trim($currentUser->first_name . ' ' . $currentUser->last_name);
547
548 if (!$name) {
549 $name = $currentUser->display_name;
550 }
551
552 $currentPerson = [
553 'name' => $name,
554 'email' => $currentUser->user_email,
555 'user_id' => $currentUser->ID
556 ];
557 } else {
558 $request = $_REQUEST; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
559
560 // Check for url params
561 if ($name = sanitize_text_field(Arr::get($request, 'invitee_name'))) {
562 $currentPerson['name'] = $name;
563 }
564
565 if ($email = sanitize_email(Arr::get($request, 'invitee_email'))) {
566 if (is_email($email)) {
567 $currentPerson['email'] = $email;
568 }
569 }
570 }
571
572 if (empty($currentPerson['email'])) {
573 // Let's try to get from FluentCRM is exists
574 if (defined('FLUENTCRM')) {
575 $contactApi = FluentCrmApi('contacts');
576 $contact = $contactApi->getCurrentContact();
577 if ($contact) {
578 $currentPerson['email'] = $contact->email;
579 $currentPerson['name'] = $contact->full_name;
580 }
581 }
582 }
583
584 $globalSettings = Helper::getGlobalSettings();
585 $startDay = Arr::get($globalSettings, 'administration.start_day', 'mon');
586
587 $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')
689 ];
690
691 if (isset($_SERVER['HTTP_CF_IPCOUNTRY'])) {
692 $data['user_country'] = sanitize_text_field($_SERVER['HTTP_CF_IPCOUNTRY']); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
693 } else {
694 $data['user_country'] = Arr::get($globalSettings, 'administration.default_country', '');
695 }
696
697 return apply_filters('fluent_calendar/global_booking_vars', $data);
698 }
699
700 public function ajaxScheduleMeeting()
701 {
702 $app = App::getInstance();
703
704 $postedData = $_REQUEST; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
705
706 $eventId = (int)$postedData['event_id'];
707
708 $isRescheduling = Arr::get($postedData, 'rescheduling_hash', '');
709
710 $calendarEvent = CalendarSlot::find($eventId);
711
712 if (!$calendarEvent || ($calendarEvent->status != 'active' && !$isRescheduling)) {
713 wp_send_json([
714 ], 422);
715 }
716
717 do_action('fluent_booking/starting_scheduling_ajax', $postedData);
718
719 $rules = [
720 'name' => 'required',
721 'email' => 'required|email',
722 'timezone' => 'required',
723 'start_date' => 'required'
724 ];
725
726 $messages = [
727 'name.required' => __('Please enter your name', 'fluent-booking'),
728 'email.required' => __('Please enter your email address', 'fluent-booking'),
729 'email.email' => __('Please enter provide a valid email address', 'fluent-booking'),
730 'timezone.required' => __('Please select timezone first', 'fluent-booking'),
731 'start_date.required' => __('Please select a date and time', 'fluent-booking')
732 ];
733
734 if ($calendarEvent->isPhoneRequired()) {
735 $rules['phone_number'] = 'required';
736 $messages['phone_number.required'] = __('Please provide your phone number', 'fluent-booking');
737 } else if ($calendarEvent->isAddressRequired()) {
738 $rules['address'] = 'required';
739 $messages['address.required'] = __('Please provide your Address', 'fluent-booking');
740 } else if ($calendarEvent->isLocationFieldRequired()) {
741 $rules['location_config.driver'] = 'required';
742 $messages['location_config.driver'] = __('Please select location', 'fluent-booking');
743
744 $selectedLocation = LocationService::getLocationDetails($calendarEvent, Arr::get($postedData, 'location_config', []), $postedData);
745 $selectedLocationDriver = Arr::get($selectedLocation, 'type');
746 // is user input required
747 if (in_array($selectedLocationDriver, ['in_person_guest', 'phone_guest'])) {
748 $rules['location_config.user_location_input'] = 'required';
749 if ($selectedLocationDriver == 'in_person_guest') {
750 $messages['location_config.user_location_input.required'] = __('Please provide your address', 'fluent-booking');
751 } else {
752 $messages['location_config.user_location_input.required'] = __('Please provide your phone number', 'fluent-booking');
753 }
754 }
755 }
756
757 $duration = (int)$calendarEvent->getDuration(Arr::get($postedData, 'duration', null));
758
759 if ($calendarEvent->isPaymentEnabled($duration)) {
760 $rules['payment_method'] = 'required';
761 $messages['payment_method.required'] = __('Please select a valid payment method', 'fluent-booking');
762 }
763
764 if ($additionalGuests = Arr::get($postedData, 'guests', [])) {
765 $postedData['guests'] = array_filter(array_map('sanitize_email', $additionalGuests));
766 }
767
768 $requiredFields = array_filter($calendarEvent->getMeta('booking_fields', []), function ($field) {
769 return Arr::isTrue($field, 'required') && Arr::isTrue($field, 'enabled') && (Arr::get($field, 'name') == 'message' || Arr::get($field, 'name') == 'guests');
770 });
771
772 foreach ($requiredFields as $field) {
773 if (empty($rules[$field['name']])) {
774 $rules[$field['name']] = 'required';
775 $messages[$field['name'] . '.required'] = __('This field is required', 'fluent-booking');
776 }
777 }
778
779 $validationConfig = apply_filters('fluent_booking/schedule_validation_rules_data', [
780 'rules' => $rules,
781 'messages' => $messages
782 ], $postedData, $calendarEvent);
783
784 $validator = $app->validator->make($postedData, $validationConfig['rules'], $validationConfig['messages']);
785 if ($validator->validate()->fails()) {
786 wp_send_json([
787 'message' => __('Please fill up the required data', 'fluent-booking'),
788 'errors' => $validator->errors()
789 ], 422);
790 return;
791 }
792
793 $customFieldsData = BookingFieldService::getCustomFieldsData($postedData, $calendarEvent);
794 $customFieldsData = apply_filters('fluent_booking/schedule_custom_field_data', $customFieldsData, $customFieldsData, $calendarEvent);
795
796 if (is_wp_error($customFieldsData)) {
797 wp_send_json([
798 'message' => $customFieldsData->get_error_message(),
799 'errors' => $customFieldsData->get_error_data()
800 ], 422);
801 return;
802 }
803
804 $startDate = sanitize_text_field(Arr::get($postedData, 'start_date'));
805 $timezone = sanitize_text_field(Arr::get($postedData, 'timezone', 'UTC'));
806
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
809
810 $bookingData = [
811 'person_time_zone' => sanitize_text_field($timezone),
812 'start_time' => $startDateTime,
813 'name' => sanitize_text_field($postedData['name']),
814 'email' => sanitize_email($postedData['email']),
815 'message' => sanitize_textarea_field(wp_unslash(Arr::get($postedData, 'message', ''))),
816 'phone' => sanitize_textarea_field(Arr::get($postedData, 'phone_number', '')),
817 'address' => sanitize_textarea_field(Arr::get($postedData, 'address', '')),
818 'ip_address' => Helper::getIp(),
819 'status' => 'scheduled',
820 'source' => 'web',
821 'event_type' => $calendarEvent->event_type,
822 'slot_minutes' => $duration
823 ];
824
825 if ($calendarEvent->isConfirmationRequired($startDateTime)) {
826 $bookingData['status'] = 'pending';
827 }
828
829 $selectedLocation = LocationService::getLocationDetails($calendarEvent, Arr::get($postedData, 'location_config', []), $postedData);
830 if ($selectedLocation['type'] == 'phone_guest') {
831 $bookingData['phone'] = $selectedLocation['description'];
832 }
833
834 $bookingData['location_details'] = $selectedLocation;
835
836 if ($sourceUrl = Arr::get($postedData, 'source_url', '')) {
837 $bookingData['source_url'] = sanitize_url($sourceUrl);
838 }
839
840 if (!empty($postedData['payment_method'])) {
841 $customFieldsData['payment_method'] = $postedData['payment_method'];
842 }
843
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);
848 }
849
850 $timeSlotService = TimeSlotServiceHandler::initService($calendarEvent->calendar, $calendarEvent);
851
852 if (is_wp_error($timeSlotService)) {
853 return TimeSlotServiceHandler::sendError($timeSlotService, $calendarEvent, $timezone);
854 }
855
856 $isSpotAvailable = $timeSlotService->isSpotAvailable($startDateTime, $endDateTime, $duration);
857
858 if (!$isSpotAvailable) {
859 wp_send_json([
860 'message' => __('This selected time slot is not available. Maybe someone booked the spot just a few seconds ago.', 'fluent-booking')
861 ], 422);
862 }
863
864 if ($calendarEvent->isTeamEvent()) {
865 $bookingData['host_user_id'] = $timeSlotService->hostUserId;
866 }
867
868 do_action('fluent_booking/before_creating_schedule', $bookingData, $postedData, $calendarEvent);
869
870 try {
871 $booking = BookingService::createBooking($bookingData, $calendarEvent, $customFieldsData);
872
873 if (is_wp_error($booking)) {
874 throw new \Exception(wp_kses_post($booking->get_error_message()), 422);
875 }
876
877 } catch (\Exception $e) {
878 wp_send_json([
879 'message' => $e->getMessage()
880 ], 422);
881 return;
882 }
883
884 $redirectUrl = $booking->getRedirectUrlWithQuery();
885
886 $html = BookingService::getBookingConfirmationHtml($booking);
887
888 wp_send_json(apply_filters('fluent_booking/booking_confirmation_response', [
889 'message' => __('Booking has been confirmed', 'fluent-booking'),
890 'redirect_url' => $redirectUrl,
891 'response_html' => $html,
892 'booking_hash' => $booking->hash
893 ], $booking), 200);
894 }
895
896 public function ajaxGetAvailableDates()
897 {
898 $startBenchmark = microtime(true);
899
900 $request = $_REQUEST; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
901
902 $eventId = (int)$request['event_id'];
903
904 $rescheduling = Arr::get($request, 'rescheduling', 'no');
905
906 $calendarEvent = CalendarSlot::findOrfail($eventId);
907
908 if (!$calendarEvent || ($calendarEvent->status != 'active' && $rescheduling == 'no')) {
909 wp_send_json([
910 'message' => __('Sorry, the host is not accepting any new bookings at the moment.', 'fluent-booking')
911 ], 422);
912 }
913
914 $calendar = $calendarEvent->calendar;
915 $startDate = sanitize_text_field(Arr::get($request, 'start_date')); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
916
917 if (!$startDate) {
918 $startDate = gmdate('Y-m-d H:i:s'); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
919 }
920
921 $timeZone = sanitize_text_field(Arr::get($request, 'timezone')); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
922
923 if (!$timeZone) {
924 $timeZone = wp_timezone_string();
925 }
926
927 if (!in_array($timeZone, \DateTimeZone::listIdentifiers())) {
928 $timeZone = $calendar->author_timezone;
929 }
930
931 $duration = (int)$calendarEvent->getDuration(Arr::get($request, 'duration', null));
932
933 $timeSlotService = TimeSlotServiceHandler::initService($calendar, $calendarEvent);
934
935 if (is_wp_error($timeSlotService)) {
936 return TimeSlotServiceHandler::sendError($timeSlotService, $calendarEvent, $timeZone);
937 }
938
939 $availableSpots = $timeSlotService->getAvailableSpots($startDate, $timeZone, $duration);
940
941 if (is_wp_error($availableSpots)) {
942 return TimeSlotServiceHandler::sendError($availableSpots, $calendarEvent, $timeZone);
943 }
944
945 $availableSpots = array_filter($availableSpots);
946 $availableSpots = apply_filters('fluent_booking/available_slots_for_view', $availableSpots, $calendarEvent, $calendar, $timeZone, $duration);
947
948 wp_send_json([
949 'available_slots' => $availableSpots,
950 'timezone' => $timeZone,
951 'max_lookup_date' => $calendarEvent->getMaxLookUpDate(),
952 'execution_time' => microtime(true) - $startBenchmark
953 ], 200);
954 }
955
956 public function getCalendarEventVars(Calendar $calendar, CalendarSlot $calendarEvent)
957 {
958 $calendarEvent->description = wpautop($calendarEvent->description);
959 $calendarEvent->location_icon_html = $calendarEvent->defaultLocationHtml();
960 $formFields = BookingFieldService::getBookingFields($calendarEvent);
961
962 $eventData = [
963 'id' => $calendarEvent->id,
964 'max_lookup_date' => $calendarEvent->getMaxLookUpDate(),
965 'min_lookup_date' => $calendarEvent->getMinLookUpDate(),
966 'min_bookable_date' => $calendarEvent->getMinBookableDateTime(),
967 'duration' => $calendarEvent->getDefaultDuration(),
968 'title' => $calendarEvent->title,
969 'location_settings' => $calendarEvent->location_settings,
970 'location_icon_html' => $calendarEvent->location_icon_html,
971 'description' => $calendarEvent->description,
972 'pre_selects' => null,
973 'settings' => $calendarEvent->settings,
974 'type' => $calendarEvent->type,
975 'event_type' => $calendarEvent->event_type,
976 'time_format' => Arr::get(get_option('_fluent_booking_settings'), 'time_format', '12'),
977 ];
978
979 $author = $calendar->getAuthorProfile(true);
980 $author['name'] = $calendar->title;
981
982 $eventVars = [
983 'slot' => $eventData,
984 'author_profile' => $author,
985 'form_fields' => $formFields,
986 'i18n' => [
987 'Schedule_Meeting' => __('Schedule Meeting', 'fluent-booking'),
988 'Continue_to_Payments' => __('Continue to Payments', 'fluent-booking'),
989 'Confirm_Payment' => __('Confirm Payment', 'fluent-booking'),
990 ],
991 'date_formatter' => DateTimeHelper::getDateFormatter(true),
992 'isRtl' => Helper::fluentbooking_is_rtl(),
993 'duration_lookup' => Helper::getDurationLookup(),
994 'multi_duration_lookup' => Helper::getDurationLookup(true)
995 ];
996
997 $eventVars['form_fields'] = array_values($eventVars['form_fields']);
998
999 if (!$calendar->isHostCalendar()) {
1000 $eventVars['team_member_profiles'] = $calendarEvent->getAuthorProfiles(true);
1001 }
1002
1003 return apply_filters('fluent_booking/public_event_vars', $eventVars, $calendarEvent);
1004 }
1005
1006 public function ajaxHandleCancelMeeting()
1007 {
1008 $data = $_REQUEST; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
1009
1010 $meetingHash = Arr::get($data, 'meeting_hash');
1011
1012 $meeting = Booking::where('hash', $meetingHash)->first();
1013
1014 if (!$meeting) {
1015 wp_send_json([
1016 'message' => __('Sorry! meeting could not be found', 'fluent-booking')
1017 ], 422);
1018 }
1019
1020 if (!$meeting->canCancel()) {
1021 wp_send_json([
1022 'message' => $meeting->getCancellationMessage()
1023 ], 422);
1024 }
1025
1026 $message = sanitize_textarea_field(Arr::get($data, 'cancellation_reason', ''));
1027
1028 $cancelField = BookingFieldService::getBookingFieldByName($meeting->calendar_event, 'cancellation_reason');
1029
1030 if (!$message && Arr::isTrue($cancelField, 'required')) {
1031 wp_send_json([
1032 'message' => __('Please provide a reason for cancellation', 'fluent-booking')
1033 ], 422);
1034 }
1035
1036 $result = $meeting->cancelMeeting($message, 'guest', get_current_user_id());
1037
1038 if (is_wp_error($result)) {
1039 if (!wp_doing_ajax()) {
1040 wp_redirect($meeting->getConfirmationUrl());
1041 exit();
1042 }
1043
1044 wp_send_json([
1045 'message' => $result->get_error_message()
1046 ], 422);
1047 }
1048
1049 if (wp_doing_ajax()) {
1050 wp_send_json([
1051 'message' => __('Meeting has been cancelled', 'fluent-booking')
1052 ], 200);
1053 }
1054
1055 wp_redirect($meeting->getConfirmationUrl());
1056 exit;
1057 }
1058 }
1059