PluginProbe
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution / 1.5.01
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution v1.5.01
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
fluent-booking / app / Hooks / Handlers / FrontEndHandler.php

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

1,057 lines 47.8 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 $calendarEvents[$event->calendar_id][] = $event;
256 }
257
258 $calendars = Calendar::query()->whereIn('id', $calendarIds)->get();
259
260 foreach ($calendars as $calendar) {
261 $calendar->activeEvents = $calendarEvents[$calendar->id];
262 }
263
264 return $this->renderTeamHosts($calendars, [
265 'title' => $atts['title'],
266 'description' => $atts['description'],
267 'logo' => $atts['logo_url'],
268 'wrapper_class' => ''
269 ]);
270 }
271
272 public function renderTeamHosts($calendars, $headerConfig = [])
273 {
274 $wrapperId = 'fcal_team_' . Helper::getNextIndex();
275 wp_enqueue_script('fluent-booking-team', App::getInstance('url.assets') . 'public/js/team_app.js', [], FLUENT_BOOKING_ASSETS_VERSION, true);
276
277 $vars = [];
278 foreach ($calendars as $calendar) {
279 $hostHtml = (string)(string)\FluentBooking\App\App::getInstance('view')->make('landing.author_html', [
280 'author' => $calendar->getAuthorProfile(),
281 'calendar' => $calendar,
282 'events' => $calendar->activeEvents
283 ]);
284
285 $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>';
286
287 $eventCount = count($calendar->activeEvents);
288
289 $vars['fcal_host_' . $calendar->id] = [
290 'host_html' => $hostHtml,
291 'event_count' => $eventCount,
292 'target_event_id' => ($eventCount == 1) ? $calendar->activeEvents[0]->id : 0
293 ];
294
295 foreach ($calendar->activeEvents as $event) {
296 $itemVars = $this->getCalendarEventVars($event->calendar, $event);
297 $extraJs = (new LandingPageHandler())->getEventLandingExtraJsFiles($itemVars['form_fields'], $event);
298 if ($extraJs) {
299 $itemVars['lazy_js_files'] = $extraJs;
300 }
301 wp_localize_script('fluent-booking-team', 'fcal_public_vars_' . $event->calendar_id . '_' . $event->id, $itemVars);
302 }
303 }
304
305 wp_localize_script('fluent-booking-team', $wrapperId, $vars);
306
307 $assetUrl = App::getInstance('url.assets');
308 wp_enqueue_script('fluent-booking-public', $assetUrl . 'public/js/app.js', [], FLUENT_BOOKING_ASSETS_VERSION, true);
309 $this->loadGlobalVars();
310
311 return App::make('view')->make('public.team_page', [
312 'hosts' => $calendars,
313 'wrapper_id' => $wrapperId,
314 'logo' => Arr::get($headerConfig, 'logo', ''),
315 'title' => Arr::get($headerConfig, 'title', ''),
316 'description' => Arr::get($headerConfig, 'description', ''),
317 'wrapper_class' => Arr::get($headerConfig, 'wrapper_class', '')
318 ]);
319 }
320
321 public function handleCalendarShortcode($atts, $content)
322 {
323 $atts = shortcode_atts([
324 'calendar_id' => '',
325 'event_ids' => '',
326 'title' => '',
327 'description' => '',
328 'logo' => '',
329 'hide_info' => false
330 ], $atts);
331
332 $calendarId = intval($atts['calendar_id']);
333 $title = sanitize_text_field($atts['title']);
334 $description = sanitize_text_field($atts['description']);
335 $logo = sanitize_text_field($atts['logo']);
336 $hideInfo = $atts['hide_info'] ? true : false;
337 $eventIds = array_filter(array_map('intval', explode(',', $atts['event_ids'])));
338
339 if (!$calendarId) {
340 return '';
341 }
342
343 $calendar = Calendar::find($calendarId);
344 if (!$calendar) {
345 return '';
346 }
347
348 $calendarEventQuery = CalendarSlot::where('calendar_id', $calendar->id)
349 ->where('status', 'active');
350
351 if ($eventIds && $eventIds != 'all') {
352 $calendarEventQuery->whereIn('id', $eventIds);
353 }
354
355 $calendarEvents = $calendarEventQuery->get();
356
357 if ($calendarEvents->isEmpty()) {
358 return '';
359 }
360
361 foreach ($calendarEvents as $event) {
362 $event->public_url = $event->getPublicUrl();
363 $event->durations = $event->getAvailableDurations();
364 $event->description = $event->getDescription();
365 $event->short_description = Helper::excerpt($event->description);
366 }
367
368 $calendar->activeEvents = $calendarEvents;
369
370 return $this->renderCalendarBlock($calendar, [
371 'title' => $title,
372 'description' => $description,
373 'logo' => $logo,
374 'hide_info' => $hideInfo,
375 'wrapper_class' => '',
376 ]);
377 }
378
379 public function renderCalendarBlock($calendar, $headerConfig = [])
380 {
381 $wrapperId = 'fcal_team_' . Helper::getNextIndex();
382 wp_enqueue_script('fluent-booking-calendar', App::getInstance('url.assets') . 'public/js/calendar_app.js', [], FLUENT_BOOKING_ASSETS_VERSION, true);
383
384 $calendarHtml = (string)(string)\FluentBooking\App\App::getInstance('view')->make('landing.author_html', [
385 'author' => $calendar->getAuthorProfile(),
386 'calendar' => $calendar,
387 'events' => $calendar->activeEvents,
388 'hideInfo' => Arr::isTrue($headerConfig, 'hide_info'),
389 'block' => true
390 ]);
391
392 $eventCount = count($calendar->activeEvents);
393
394 $vars['fcal_host_calendar' ] = [
395 'calendar_html' => $calendarHtml,
396 'event_count' => $eventCount,
397 'target_event_id' => ($eventCount == 1) ? $calendar->activeEvents[0]->id : 0
398 ];
399
400 foreach ($calendar->activeEvents as $event) {
401 $itemVars = $this->getCalendarEventVars($event->calendar, $event);
402 $extraJs = (new LandingPageHandler())->getEventLandingExtraJsFiles($itemVars['form_fields'], $event);
403 if ($extraJs) {
404 $itemVars['lazy_js_files'] = $extraJs;
405 }
406 wp_localize_script('fluent-booking-calendar', 'fcal_public_vars_' . $event->calendar_id . '_' . $event->id, $itemVars);
407 }
408
409 wp_localize_script('fluent-booking-calendar', $wrapperId, $vars);
410
411 $assetUrl = App::getInstance('url.assets');
412 wp_enqueue_script('fluent-booking-public', $assetUrl . 'public/js/app.js', [], FLUENT_BOOKING_ASSETS_VERSION, true);
413 $this->loadGlobalVars();
414
415 return App::make('view')->make('public.calendar_page', [
416 'calendar' => $calendar,
417 'wrapper_id' => $wrapperId,
418 'logo' => Arr::get($headerConfig, 'logo', ''),
419 'title' => Arr::get($headerConfig, 'title', ''),
420 'description' => Arr::get($headerConfig, 'description', ''),
421 'wrapper_class' => Arr::get($headerConfig, 'wrapper_class', '')
422 ]);
423 }
424
425 public function handleBookingListsShortcode($atts, $content)
426 {
427 $atts = shortcode_atts([
428 'title' => __('My Bookings', 'fluent-booking'),
429 'filter' => 'show',
430 'pagination' => 'show',
431 'period' => 'all',
432 'calendar_ids' => 'all',
433 'no_bookings' => __('No bookings found', 'fluent-booking'),
434 'per_page' => 10
435 ], $atts);
436
437 $atts['title'] = sanitize_text_field($atts['title']);
438 $atts['filter'] = sanitize_text_field($atts['filter']);
439 $atts['pagination'] = sanitize_text_field($atts['pagination']);
440 $atts['no_bookings'] = sanitize_text_field($atts['no_bookings']);
441
442 $userData = get_userdata(get_current_user_id());
443
444 $userEmail = $userData ? $userData->user_email : null;
445
446 if (!$userEmail) {
447 return __('Please login to view your bookings', 'fluent-booking');
448 }
449
450 $data = $_REQUEST; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
451
452 $perPage = intval(Arr::get($data, 'booking_per_page', $atts['per_page']));
453 $currentPage = intval(Arr::get($data, 'booking_page', 1));
454 $bookingPeriod = sanitize_text_field(Arr::get($data, 'booking_period', $atts['period']));
455
456 $bookingQuery = Booking::query()->with('calendar_event')
457 ->where('email', $userEmail)
458 ->orderBy('start_time', 'DESC')
459 ->applyComputedStatus($bookingPeriod);
460
461 if ($atts['calendar_ids'] != 'all') {
462 $atts['calendar_ids'] = array_map('intval', explode(',', $atts['calendar_ids']));
463 $bookingQuery->whereIn('calendar_id', $atts['calendar_ids']);
464 }
465
466 $bookings = $bookingQuery->paginate($perPage, ['*'], 'booking_page', $currentPage)
467 ->appends(['booking_page' => $currentPage])
468 ->withQueryString();
469
470 foreach ($bookings as &$booking) {
471 $booking->author_name = $booking->getHostDetails(false)['name'];
472 $booking->happening_status = $booking->getOngoingStatus();
473 $booking->booking_status_text = $booking->getBookingStatus();
474 $booking->payment_status_text = $booking->getPaymentStatus();
475
476 $booking->booking_date = DateTimeHelper::formatToLocale($booking->getAttendeeStartTime(), 'date');
477 $booking->booking_time = DateTimeHelper::formatToLocale($booking->getAttendeeEndTime(), 'time') . ' - ' . DateTimeHelper::formatToLocale($booking->getAttendeeEndTime(), 'time');
478 }
479
480 $currentPage = $bookings->currentPage();
481 $lastPage = $bookings->lastPage();
482 $startPage = max(1, $currentPage - 2);
483 $endPage = min($lastPage, $currentPage + 2);
484
485 // Adjust if near the beginning or the end
486 if ($currentPage < 3) {
487 $endPage = min($lastPage, 5);
488 }
489 if ($currentPage > $lastPage - 2) {
490 $startPage = max(1, $lastPage - 4);
491 }
492
493 $periodOptions = Helper::getBookingPeriodOptions();
494
495 $pageOptions = apply_filters('fluent_booking/booking_per_page_options', [5, 10, 15, 20, 50, 100]);
496
497 wp_enqueue_script('fluent-booking-list', App::getInstance('url.assets') . 'public/js/bookings.js', [], FLUENT_BOOKING_ASSETS_VERSION, true);
498
499 return App::make('view')->make('public.bookings', [
500 'bookings' => $bookings,
501 'attributes' => $atts,
502 'per_page' => $perPage,
503 'start_page' => $startPage,
504 'end_page' => $endPage,
505 'booking_period' => $bookingPeriod,
506 'page_options' => $pageOptions,
507 'period_options' => $periodOptions
508 ]);
509 }
510
511 public function handleReceiptShortcode($atts, $content)
512 {
513 if (!isset($_REQUEST['hash'])) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
514 return __('Booking hash is missing!', 'fluent-booking');
515 }
516
517 $hash = sanitize_text_field($_REQUEST['hash']); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
518
519 return apply_filters('fluent_booking/payment_receipt_html', '', $hash);
520 }
521
522 private function loadGlobalVars()
523 {
524 static $loaded;
525
526 if ($loaded) {
527 return;
528 }
529
530 $loaded = true;
531
532 wp_localize_script('fluent-booking-public', 'fluentCalendarPublicVars', $this->getGlobalVars());
533 }
534
535 public function getGlobalVars()
536 {
537 $currentPerson = [
538 'name' => '',
539 'email' => ''
540 ];
541
542 if (is_user_logged_in()) {
543 $currentUser = wp_get_current_user();
544 $name = trim($currentUser->first_name . ' ' . $currentUser->last_name);
545
546 if (!$name) {
547 $name = $currentUser->display_name;
548 }
549
550 $currentPerson = [
551 'name' => $name,
552 'email' => $currentUser->user_email,
553 'user_id' => $currentUser->ID
554 ];
555 } else {
556 $request = $_REQUEST; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
557
558 // Check for url params
559 if ($name = sanitize_text_field(Arr::get($request, 'invitee_name'))) {
560 $currentPerson['name'] = $name;
561 }
562
563 if ($email = sanitize_email(Arr::get($request, 'invitee_email'))) {
564 if (is_email($email)) {
565 $currentPerson['email'] = $email;
566 }
567 }
568 }
569
570 if (empty($currentPerson['email'])) {
571 // Let's try to get from FluentCRM is exists
572 if (defined('FLUENTCRM')) {
573 $contactApi = FluentCrmApi('contacts');
574 $contact = $contactApi->getCurrentContact();
575 if ($contact) {
576 $currentPerson['email'] = $contact->email;
577 $currentPerson['name'] = $contact->full_name;
578 }
579 }
580 }
581
582 $globalSettings = Helper::getGlobalSettings();
583 $startDay = Arr::get($globalSettings, 'administration.start_day', 'mon');
584
585 $data = [
586 'ajaxurl' => admin_url('admin-ajax.php'),
587 'timezones' => DateTimeHelper::getFlatGroupedTimeZones(),
588 'current_person' => $currentPerson,
589 'start_day' => $startDay,
590 'i18' => [
591 'Timezone' => __('Timezone', 'fluent-booking'),
592 'Minutes' => __('Minutes', 'fluent-booking'),
593 'Enter Details' => __('Enter Details', 'fluent-booking'),
594 'Summary' => __('Summary', 'fluent-booking'),
595 'Payment Details' => __('Payment Details', 'fluent-booking'),
596 'Total Payment' => __('Total Payment', 'fluent-booking'),
597 'Payment Method' => __('Payment Method', 'fluent-booking'),
598 'Pay Now' => __('Pay Now', 'fluent-booking'),
599 'processing' => __('Processing', 'fluent-booking'),
600 'date_time_config' => [
601 'weekdays' => array(
602 'sunday' => _x('Sunday', 'calendar day full', 'fluent-booking'),
603 'monday' => _x('Monday', 'calendar day full', 'fluent-booking'),
604 'tuesday' => _x('Tuesday', 'calendar day full', 'fluent-booking'),
605 'wednesday' => _x('Wednesday', 'calendar day full', 'fluent-booking'),
606 'thursday' => _x('Thursday', 'calendar day full', 'fluent-booking'),
607 'friday' => _x('Friday', 'calendar day full', 'fluent-booking'),
608 'saturday' => _x('Saturday', 'calendar day full', 'fluent-booking'),
609 ),
610 'months' => array(
611 'January' => _x('January', 'calendar month name full', 'fluent-booking'),
612 'February' => _x('February', 'calendar month name full', 'fluent-booking'),
613 'March' => _x('March', 'calendar month name full', 'fluent-booking'),
614 'April' => _x('April', 'calendar month name full', 'fluent-booking'),
615 'May' => _x('May', 'calendar month name full', 'fluent-booking'),
616 'June' => _x('June', 'calendar month name full', 'fluent-booking'),
617 'July' => _x('July', 'calendar month name full', 'fluent-booking'),
618 'August' => _x('August', 'calendar month name full', 'fluent-booking'),
619 'September' => _x('September', 'calendar month name full', 'fluent-booking'),
620 'October' => _x('October', 'calendar month name full', 'fluent-booking'),
621 'November' => _x('November', 'calendar month name full', 'fluent-booking'),
622 'December' => _x('December', 'calendar month name full', 'fluent-booking')
623 ),
624 'weekdaysShort' => array(
625 'sun' => _x('Sun', 'calendar day short', 'fluent-booking'),
626 'mon' => _x('Mon', 'calendar day short', 'fluent-booking'),
627 'tue' => _x('Tue', 'calendar day short', 'fluent-booking'),
628 'wed' => _x('Wed', 'calendar day short', 'fluent-booking'),
629 'thu' => _x('Thu', 'calendar day short', 'fluent-booking'),
630 'fri' => _x('Fri', 'calendar day short', 'fluent-booking'),
631 'sat' => _x('Sat', 'calendar day short', 'fluent-booking')
632 ),
633 'monthsShort' => array(
634 'jan' => _x('Jan', 'calendar month name short', 'fluent-booking'),
635 'feb' => _x('Feb', 'calendar month name short', 'fluent-booking'),
636 'mar' => _x('Mar', 'calendar month name short', 'fluent-booking'),
637 'apr' => _x('Apr', 'calendar month name short', 'fluent-booking'),
638 'may' => _x('May', 'calendar month name short', 'fluent-booking'),
639 'jun' => _x('Jun', 'calendar month name short', 'fluent-booking'),
640 'jul' => _x('Jul', 'calendar month name short', 'fluent-booking'),
641 'aug' => _x('Aug', 'calendar month name short', 'fluent-booking'),
642 'sep' => _x('Sep', 'calendar month name short', 'fluent-booking'),
643 'oct' => _x('Oct', 'calendar month name short', 'fluent-booking'),
644 'nov' => _x('Nov', 'calendar month name short', 'fluent-booking'),
645 'dec' => _x('Dec', 'calendar month name short', 'fluent-booking')
646 ),
647 'numericSystem' => _x('0_1_2_3_4_5_6_7_8_9', 'calendar numeric system - Sequence must need to maintained', 'fluent-booking'),
648 ],
649 'Country' => __('Country', 'fluent-booking'),
650 '12h' => _x('12h', 'date time format switch', 'fluent-booking'),
651 '24h' => _x('24h', 'date time format switch', 'fluent-booking'),
652 'spots left' => _x('spots left', 'for how many spots left for available booking', 'fluent-booking'),
653 'spots remaining' => _x('spots remaining', 'for how many spots remaining for available booking', 'fluent-booking'),
654 'Next' => _x('Next', 'Booking form spot selection', 'fluent-booking'),
655 'Select on the Next Step' => __('Select on the Next Step', 'fluent-booking'),
656 'location options' => __('location options', 'fluent-booking'),
657 'Your address' => __('Your address', 'fluent-booking'),
658 'Organizer Phone Number' => __('Organizer Phone Number', 'fluent-booking'),
659 'In Person (Attendee Address)' => __('In Person (Attendee Address)', 'fluent-booking'),
660 'In Person (Organizer Address)' => __('In Person (Organizer Address)', 'fluent-booking'),
661 'Attendee Phone Number' => __('Attendee Phone Number', 'fluent-booking'),
662 'Google Meet' => __('Google Meet', 'fluent-booking'),
663 'Zoom Meeting' => __('Zoom Meeting', 'fluent-booking'),
664 'Online Meeting' => __('Online Meeting', 'fluent-booking'),
665 'Phone Call' => __('Phone Call', 'fluent-booking'),
666 'Processing...' => __('Processing...', 'fluent-booking'),
667 'Loading Payment Processor...' => __('Loading Payment Processor...', 'fluent-booking'),
668 'PM' => __('PM', 'fluent-booking'),
669 'AM' => __('AM', 'fluent-booking'),
670 'Email' => __('Email', 'fluent-booking'),
671 'Date' => __('Date', 'fluent-booking'),
672 'Time' => __('Time', 'fluent-booking'),
673 'Add guests' => __('Add guests', 'fluent-booking'),
674 'Add another' => __('Add another', 'fluent-booking'),
675 'This field is required.' => __('This field is required.', 'fluent-booking'),
676 'No availability in' => __('No availability in', 'fluent-booking'),
677 'View next month' => __('View next month', 'fluent-booking'),
678 'View previous month' => __('View previous month', 'fluent-booking'),
679 'No_payment_method_description' => __('No activated payment method found. If you are an admin please check the event payment settings', 'fluent-booking'),
680 'Please fill up the required data' => __('Please fill up the required data', 'fluent-booking'),
681 'Please select a valid payment method' => __('Please select a valid payment method', 'fluent-booking'),
682 'Please Select' => __('Please Select', 'fluent-booking'),
683 'Something is wrong!' => __('Something is wrong!', 'fluent-booking'),
684 'Requires Confirmation' => __('Requires Confirmation', 'fluent-booking'),
685 ],
686 'theme' => Arr::get(get_option('_fluent_booking_settings'), 'theme','system-default')
687 ];
688
689 if (isset($_SERVER['HTTP_CF_IPCOUNTRY'])) {
690 $data['user_country'] = sanitize_text_field($_SERVER['HTTP_CF_IPCOUNTRY']); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
691 } else {
692 $data['user_country'] = Arr::get($globalSettings, 'administration.default_country', '');
693 }
694
695 return apply_filters('fluent_calendar/global_booking_vars', $data);
696 }
697
698 public function ajaxScheduleMeeting()
699 {
700 $app = App::getInstance();
701
702 $postedData = $_REQUEST; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
703
704 $eventId = (int)$postedData['event_id'];
705
706 $isRescheduling = Arr::get($postedData, 'rescheduling_hash', '');
707
708 $calendarEvent = CalendarSlot::find($eventId);
709
710 if (!$calendarEvent || ($calendarEvent->status != 'active' && !$isRescheduling)) {
711 wp_send_json([
712 ], 422);
713 }
714
715 do_action('fluent_booking/starting_scheduling_ajax', $postedData);
716
717 $rules = [
718 'name' => 'required',
719 'email' => 'required|email',
720 'timezone' => 'required',
721 'start_date' => 'required'
722 ];
723
724 $messages = [
725 'name.required' => __('Please enter your name', 'fluent-booking'),
726 'email.required' => __('Please enter your email address', 'fluent-booking'),
727 'email.email' => __('Please enter provide a valid email address', 'fluent-booking'),
728 'timezone.required' => __('Please select timezone first', 'fluent-booking'),
729 'start_date.required' => __('Please select a date and time', 'fluent-booking')
730 ];
731
732 if ($calendarEvent->isPhoneRequired()) {
733 $rules['phone_number'] = 'required';
734 $messages['phone_number.required'] = __('Please provide your phone number', 'fluent-booking');
735 } else if ($calendarEvent->isAddressRequired()) {
736 $rules['address'] = 'required';
737 $messages['address.required'] = __('Please provide your Address', 'fluent-booking');
738 } else if ($calendarEvent->isLocationFieldRequired()) {
739 $rules['location_config.driver'] = 'required';
740 $messages['location_config.driver'] = __('Please select location', 'fluent-booking');
741
742 $selectedLocation = LocationService::getLocationDetails($calendarEvent, Arr::get($postedData, 'location_config', []), $postedData);
743 $selectedLocationDriver = Arr::get($selectedLocation, 'type');
744 // is user input required
745 if (in_array($selectedLocationDriver, ['in_person_guest', 'phone_guest'])) {
746 $rules['location_config.user_location_input'] = 'required';
747 if ($selectedLocationDriver == 'in_person_guest') {
748 $messages['location_config.user_location_input.required'] = __('Please provide your address', 'fluent-booking');
749 } else {
750 $messages['location_config.user_location_input.required'] = __('Please provide your phone number', 'fluent-booking');
751 }
752 }
753 }
754
755 $duration = (int)$calendarEvent->getDuration(Arr::get($postedData, 'duration', null));
756
757 if ($calendarEvent->isPaymentEnabled($duration)) {
758 $rules['payment_method'] = 'required';
759 $messages['payment_method.required'] = __('Please select a valid payment method', 'fluent-booking');
760 }
761
762 if ($additionalGuests = Arr::get($postedData, 'guests', [])) {
763 $postedData['guests'] = array_filter(array_map('sanitize_email', $additionalGuests));
764 }
765
766 $requiredFields = array_filter($calendarEvent->getMeta('booking_fields', []), function ($field) {
767 return Arr::isTrue($field, 'required') && Arr::isTrue($field, 'enabled') && (Arr::get($field, 'name') == 'message' || Arr::get($field, 'name') == 'guests');
768 });
769
770 foreach ($requiredFields as $field) {
771 if (empty($rules[$field['name']])) {
772 $rules[$field['name']] = 'required';
773 $messages[$field['name'] . '.required'] = __('This field is required', 'fluent-booking');
774 }
775 }
776
777 $validationConfig = apply_filters('fluent_booking/schedule_validation_rules_data', [
778 'rules' => $rules,
779 'messages' => $messages
780 ], $postedData, $calendarEvent);
781
782 $validator = $app->validator->make($postedData, $validationConfig['rules'], $validationConfig['messages']);
783 if ($validator->validate()->fails()) {
784 wp_send_json([
785 'message' => __('Please fill up the required data', 'fluent-booking'),
786 'errors' => $validator->errors()
787 ], 422);
788 return;
789 }
790
791 $customFieldsData = BookingFieldService::getCustomFieldsData($postedData, $calendarEvent);
792 $customFieldsData = apply_filters('fluent_booking/schedule_custom_field_data', $customFieldsData, $customFieldsData, $calendarEvent);
793
794 if (is_wp_error($customFieldsData)) {
795 wp_send_json([
796 'message' => $customFieldsData->get_error_message(),
797 'errors' => $customFieldsData->get_error_data()
798 ], 422);
799 return;
800 }
801
802 $startDate = sanitize_text_field(Arr::get($postedData, 'start_date'));
803 $timezone = sanitize_text_field(Arr::get($postedData, 'timezone', 'UTC'));
804
805 $startDateTime = DateTimeHelper::convertToUtc($startDate, $timezone);
806 $endDateTime = gmdate('Y-m-d H:i:s', strtotime($startDateTime) + ($duration * 60)); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
807
808 $bookingData = [
809 'person_time_zone' => sanitize_text_field($timezone),
810 'start_time' => $startDateTime,
811 'name' => sanitize_text_field($postedData['name']),
812 'email' => sanitize_email($postedData['email']),
813 'message' => sanitize_textarea_field(wp_unslash(Arr::get($postedData, 'message', ''))),
814 'phone' => sanitize_textarea_field(Arr::get($postedData, 'phone_number', '')),
815 'address' => sanitize_textarea_field(Arr::get($postedData, 'address', '')),
816 'ip_address' => Helper::getIp(),
817 'status' => 'scheduled',
818 'source' => 'web',
819 'event_type' => $calendarEvent->event_type,
820 'slot_minutes' => $duration
821 ];
822
823 if ($calendarEvent->isConfirmationRequired($startDateTime)) {
824 $bookingData['status'] = 'pending';
825 }
826
827 $selectedLocation = LocationService::getLocationDetails($calendarEvent, Arr::get($postedData, 'location_config', []), $postedData);
828 if ($selectedLocation['type'] == 'phone_guest') {
829 $bookingData['phone'] = $selectedLocation['description'];
830 }
831
832 $bookingData['location_details'] = $selectedLocation;
833
834 if ($sourceUrl = Arr::get($postedData, 'source_url', '')) {
835 $bookingData['source_url'] = sanitize_url($sourceUrl);
836 }
837
838 if (!empty($postedData['payment_method'])) {
839 $customFieldsData['payment_method'] = $postedData['payment_method'];
840 }
841
842 if ($additionalGuests) {
843 $guestField = BookingFieldService::getBookingFieldByName($calendarEvent, 'guests');
844 $guestLimit = Arr::get($guestField, 'limit', 10);
845 $bookingData['additional_guests'] = array_slice($additionalGuests, 0, $guestLimit);
846 }
847
848 $timeSlotService = TimeSlotServiceHandler::initService($calendarEvent->calendar, $calendarEvent);
849
850 if (is_wp_error($timeSlotService)) {
851 return TimeSlotServiceHandler::sendError($timeSlotService, $calendarEvent, $timezone);
852 }
853
854 $isSpotAvailable = $timeSlotService->isSpotAvailable($startDateTime, $endDateTime, $duration);
855
856 if (!$isSpotAvailable) {
857 wp_send_json([
858 'message' => __('This selected time slot is not available. Maybe someone booked the spot just a few seconds ago.', 'fluent-booking')
859 ], 422);
860 }
861
862 if ($calendarEvent->isTeamEvent()) {
863 $bookingData['host_user_id'] = $timeSlotService->hostUserId;
864 }
865
866 do_action('fluent_booking/before_creating_schedule', $bookingData, $postedData, $calendarEvent);
867
868 try {
869 $booking = BookingService::createBooking($bookingData, $calendarEvent, $customFieldsData);
870
871 if (is_wp_error($booking)) {
872 throw new \Exception(wp_kses_post($booking->get_error_message()), 422);
873 }
874
875 } catch (\Exception $e) {
876 wp_send_json([
877 'message' => $e->getMessage()
878 ], 422);
879 return;
880 }
881
882 $redirectUrl = $booking->getRedirectUrlWithQuery();
883
884 $html = BookingService::getBookingConfirmationHtml($booking);
885
886 wp_send_json(apply_filters('fluent_booking/booking_confirmation_response', [
887 'message' => __('Booking has been confirmed', 'fluent-booking'),
888 'redirect_url' => $redirectUrl,
889 'response_html' => $html,
890 'booking_hash' => $booking->hash
891 ], $booking), 200);
892 }
893
894 public function ajaxGetAvailableDates()
895 {
896 $startBenchmark = microtime(true);
897
898 $request = $_REQUEST; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
899
900 $eventId = (int)$request['event_id'];
901
902 $rescheduling = Arr::get($request, 'rescheduling', 'no');
903
904 $calendarEvent = CalendarSlot::findOrfail($eventId);
905
906 if (!$calendarEvent || ($calendarEvent->status != 'active' && $rescheduling == 'no')) {
907 wp_send_json([
908 'message' => __('Sorry, the host is not accepting any new bookings at the moment.', 'fluent-booking')
909 ], 422);
910 }
911
912 $calendar = $calendarEvent->calendar;
913 $startDate = sanitize_text_field(Arr::get($request, 'start_date')); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
914
915 if (!$startDate) {
916 $startDate = gmdate('Y-m-d H:i:s'); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
917 }
918
919 $timeZone = sanitize_text_field(Arr::get($request, 'timezone')); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
920
921 if (!$timeZone) {
922 $timeZone = wp_timezone_string();
923 }
924
925 if (!in_array($timeZone, \DateTimeZone::listIdentifiers())) {
926 $timeZone = $calendar->author_timezone;
927 }
928
929 $duration = (int)$calendarEvent->getDuration(Arr::get($request, 'duration', null));
930
931 $timeSlotService = TimeSlotServiceHandler::initService($calendar, $calendarEvent);
932
933 if (is_wp_error($timeSlotService)) {
934 return TimeSlotServiceHandler::sendError($timeSlotService, $calendarEvent, $timeZone);
935 }
936
937 $availableSpots = $timeSlotService->getAvailableSpots($startDate, $timeZone, $duration);
938
939 if (is_wp_error($availableSpots)) {
940 return TimeSlotServiceHandler::sendError($availableSpots, $calendarEvent, $timeZone);
941 }
942
943 $availableSpots = array_filter($availableSpots);
944 $availableSpots = apply_filters('fluent_booking/available_slots_for_view', $availableSpots, $calendarEvent, $calendar, $timeZone, $duration);
945
946 wp_send_json([
947 'available_slots' => $availableSpots,
948 'timezone' => $timeZone,
949 'max_lookup_date' => $calendarEvent->getMaxLookUpDate(),
950 'execution_time' => microtime(true) - $startBenchmark
951 ], 200);
952 }
953
954 public function getCalendarEventVars(Calendar $calendar, CalendarSlot $calendarEvent)
955 {
956 $calendarEvent->description = wpautop($calendarEvent->description);
957 $calendarEvent->location_icon_html = $calendarEvent->defaultLocationHtml();
958 $formFields = BookingFieldService::getBookingFields($calendarEvent);
959
960 $eventData = [
961 'id' => $calendarEvent->id,
962 'max_lookup_date' => $calendarEvent->getMaxLookUpDate(),
963 'min_lookup_date' => $calendarEvent->getMinLookUpDate(),
964 'min_bookable_date' => $calendarEvent->getMinBookableDateTime(),
965 'duration' => $calendarEvent->getDefaultDuration(),
966 'title' => $calendarEvent->title,
967 'location_settings' => $calendarEvent->location_settings,
968 'location_icon_html' => $calendarEvent->location_icon_html,
969 'description' => $calendarEvent->description,
970 'pre_selects' => null,
971 'settings' => $calendarEvent->settings,
972 'type' => $calendarEvent->type,
973 'event_type' => $calendarEvent->event_type,
974 'time_format' => Arr::get(get_option('_fluent_booking_settings'), 'time_format', '12'),
975 ];
976
977 $author = $calendar->getAuthorProfile(true);
978 $author['name'] = $calendar->title;
979
980 $eventVars = [
981 'slot' => $eventData,
982 'author_profile' => $author,
983 'form_fields' => $formFields,
984 'i18n' => [
985 'Schedule_Meeting' => __('Schedule Meeting', 'fluent-booking'),
986 'Continue_to_Payments' => __('Continue to Payments', 'fluent-booking'),
987 'Confirm_Payment' => __('Confirm Payment', 'fluent-booking'),
988 ],
989 'date_formatter' => DateTimeHelper::getDateFormatter(true),
990 'isRtl' => Helper::fluentbooking_is_rtl(),
991 'duration_lookup' => Helper::getDurationLookup(),
992 'multi_duration_lookup' => Helper::getDurationLookup(true)
993 ];
994
995 $eventVars['form_fields'] = array_values($eventVars['form_fields']);
996
997 if (!$calendar->isHostCalendar()) {
998 $eventVars['team_member_profiles'] = $calendarEvent->getAuthorProfiles(true);
999 }
1000
1001 return apply_filters('fluent_booking/public_event_vars', $eventVars, $calendarEvent);
1002 }
1003
1004 public function ajaxHandleCancelMeeting()
1005 {
1006 $data = $_REQUEST; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
1007
1008 $meetingHash = Arr::get($data, 'meeting_hash');
1009
1010 $meeting = Booking::where('hash', $meetingHash)->first();
1011
1012 if (!$meeting) {
1013 wp_send_json([
1014 'message' => __('Sorry! meeting could not be found', 'fluent-booking')
1015 ], 422);
1016 }
1017
1018 if (!$meeting->canCancel()) {
1019 wp_send_json([
1020 'message' => $meeting->getCancellationMessage()
1021 ], 422);
1022 }
1023
1024 $message = sanitize_textarea_field(Arr::get($data, 'cancellation_reason', ''));
1025
1026 $cancelField = BookingFieldService::getBookingFieldByName($meeting->calendar_event, 'cancellation_reason');
1027
1028 if (!$message && Arr::isTrue($cancelField, 'required')) {
1029 wp_send_json([
1030 'message' => __('Please provide a reason for cancellation', 'fluent-booking')
1031 ], 422);
1032 }
1033
1034 $result = $meeting->cancelMeeting($message, 'guest', get_current_user_id());
1035
1036 if (is_wp_error($result)) {
1037 if (!wp_doing_ajax()) {
1038 wp_redirect($meeting->getConfirmationUrl());
1039 exit();
1040 }
1041
1042 wp_send_json([
1043 'message' => $result->get_error_message()
1044 ], 422);
1045 }
1046
1047 if (wp_doing_ajax()) {
1048 wp_send_json([
1049 'message' => __('Meeting has been cancelled', 'fluent-booking')
1050 ], 200);
1051 }
1052
1053 wp_redirect($meeting->getConfirmationUrl());
1054 exit;
1055 }
1056 }
1057