PluginProbe
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution / 1.5.25
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution v1.5.25
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.25, at app/Hooks/Handlers/FrontEndHandler.php

1,103 lines 49.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\CalendarEventService;
16 use FluentBooking\App\Services\LocationService;
17 use FluentBooking\App\Services\TimeSlotService;
18 use FluentBooking\App\Services\PermissionManager;
19 use FluentBooking\Framework\Support\Arr;
20
21 class FrontEndHandler
22 {
23 public function register()
24 {
25 add_shortcode('fluent_booking', [$this, 'handleBookingShortcode']);
26
27 add_shortcode('fluent_booking_team', [$this, 'handleTeamShortcode']);
28
29 add_shortcode('fluent_booking_calendar', [$this, 'handleCalendarShortcode']);
30
31 add_shortcode('fluent_booking_lists', [$this, 'handleBookingListsShortcode']);
32
33 add_shortcode('fluent_booking_receipt', [$this, 'handleReceiptShortcode']);
34
35 add_action('wp_ajax_fluent_cal_schedule_meeting', [$this, 'ajaxScheduleMeeting']);
36 add_action('wp_ajax_nopriv_fluent_cal_schedule_meeting', [$this, 'ajaxScheduleMeeting']);
37
38 add_action('wp_ajax_fcal_cancel_meeting', [$this, 'ajaxHandleCancelMeeting']);
39 add_action('wp_ajax_nopriv_fcal_cancel_meeting', [$this, 'ajaxHandleCancelMeeting']);
40
41 add_action('wp_ajax_fluent_cal_get_available_dates', [$this, 'ajaxGetAvailableDates']);
42 add_action('wp_ajax_nopriv_fluent_cal_get_available_dates', [$this, 'ajaxGetAvailableDates']);
43
44 add_action('fluent_booking/starting_scheduling_ajax', [$this, 'handleRescheduling']);
45 }
46
47 public function handleBookingShortcode($atts, $content)
48 {
49 $atts = shortcode_atts([
50 'id' => 0,
51 'theme' => 'light',
52 'disable_author' => 'no',
53 'hash' => ''
54 ], $atts);
55
56 if (!$atts['id'] && !$atts['hash']) {
57 return '';
58 }
59
60 $calendarEvent = CalendarSlot::find($atts['id']);
61 if (!$calendarEvent) {
62 $calendarEvent = CalendarSlot::where('hash', $atts['hash'])->first();
63 if (!$calendarEvent) {
64 return __('Calendar event not found', 'fluent-booking');
65 }
66 }
67
68 $calendar = $calendarEvent->calendar;
69 if (!$calendar) {
70 return __('Calendar not found', 'fluent-booking');
71 }
72
73 $assetUrl = App::getInstance('url.assets');
74
75 $localizeData = $this->getCalendarEventVars($calendar, $calendarEvent);
76 $localizeData['disable_author'] = $atts['disable_author'] == 'yes';
77 $localizeData['theme'] = $atts['theme'];
78
79 if (BookingFieldService::hasPhoneNumberField($localizeData['form_fields'])) {
80 wp_enqueue_script('fluent-booking-phone-field', $assetUrl . 'public/js/phone-field.js', [], FLUENT_BOOKING_ASSETS_VERSION, true);
81 }
82
83 wp_enqueue_script('fluent-booking-public', $assetUrl . 'public/js/app.js', [], FLUENT_BOOKING_ASSETS_VERSION, true);
84
85 $this->loadGlobalVars();
86 wp_localize_script(
87 'fluent-booking-public',
88 'fcal_public_vars_' . $calendar->id . '_' . $calendarEvent->id,
89 $localizeData,
90 );
91
92 return App::make('view')->make('public.calendar', [
93 'calenderEvent' => $calendarEvent,
94 'theme' => $atts['theme']
95 ]);
96 }
97
98 public function handleTeamShortcode($atts, $content)
99 {
100 $atts = shortcode_atts([
101 'event_ids' => '',
102 'title' => '',
103 'description' => '',
104 'logo_url' => ''
105 ], $atts);
106
107 if (!$atts['event_ids']) {
108 return '';
109 }
110
111 $eventIds = array_filter(array_map('intval', explode(',', $atts['event_ids'])));
112
113 if (empty($eventIds)) {
114 return '';
115 }
116
117 $events = CalendarSlot::query()->whereIn('id', $eventIds)
118 ->where('status', 'active')
119 ->get();
120
121 $calendarIds = [];
122 $calendarEvents = [];
123
124 foreach ($events as $event) {
125 $calendarIds[] = $event->calendar_id;
126 if (!isset($calendarEvents[$event->calendar_id])) {
127 $calendarEvents[$event->calendar_id] = [];
128 }
129 $event = CalendarEventService::processEvent($event);
130 $calendarEvents[$event->calendar_id][] = $event;
131 }
132
133 $calendars = Calendar::query()->whereIn('id', $calendarIds)->get();
134
135 foreach ($calendars as $calendar) {
136 $calendar->activeEvents = $calendarEvents[$calendar->id] ?? [];
137 $eventOrder = $calendar->getMeta('event_order');
138 if (!empty($eventOrder)) {
139 $eventsArray = $calendar->activeEvents;
140 usort($eventsArray, function($a, $b) use ($eventOrder) {
141 $posA = array_search($a->id, $eventOrder);
142 $posB = array_search($b->id, $eventOrder);
143 return $posA - $posB;
144 });
145 $calendar->activeEvents = $eventsArray;
146 }
147 }
148
149 return $this->renderTeamHosts($calendars, [
150 'title' => $atts['title'],
151 'description' => $atts['description'],
152 'logo' => $atts['logo_url'],
153 'wrapper_class' => ''
154 ]);
155 }
156
157 public function renderTeamHosts($calendars, $headerConfig = [])
158 {
159 $wrapperId = 'fcal_team_' . Helper::getNextIndex();
160 wp_enqueue_script('fluent-booking-team', App::getInstance('url.assets') . 'public/js/team_app.js', [], FLUENT_BOOKING_ASSETS_VERSION, true);
161
162 $vars = [];
163 foreach ($calendars as $calendar) {
164 $hostHtml = (string)(string)\FluentBooking\App\App::getInstance('view')->make('landing.author_html', [
165 'author' => $calendar->getAuthorProfile(),
166 'calendar' => $calendar,
167 'events' => $calendar->activeEvents
168 ]);
169
170 $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>';
171
172 $eventCount = count($calendar->activeEvents);
173
174 $vars['fcal_host_' . $calendar->id] = [
175 'host_html' => $hostHtml,
176 'event_count' => $eventCount,
177 'target_event_id' => ($eventCount == 1) ? $calendar->activeEvents[0]->id : 0
178 ];
179
180 foreach ($calendar->activeEvents as $event) {
181 $itemVars = $this->getCalendarEventVars($event->calendar, $event);
182 $extraJs = (new LandingPageHandler())->getEventLandingExtraJsFiles($itemVars['form_fields'], $event);
183 if ($extraJs) {
184 $itemVars['lazy_js_files'] = $extraJs;
185 }
186 wp_localize_script('fluent-booking-team', 'fcal_public_vars_' . $event->calendar_id . '_' . $event->id, $itemVars);
187 }
188 }
189
190 wp_localize_script('fluent-booking-team', $wrapperId, $vars);
191
192 $assetUrl = App::getInstance('url.assets');
193 wp_enqueue_script('fluent-booking-public', $assetUrl . 'public/js/app.js', [], FLUENT_BOOKING_ASSETS_VERSION, true);
194 $this->loadGlobalVars();
195
196 return App::make('view')->make('public.team_page', [
197 'hosts' => $calendars,
198 'wrapper_id' => $wrapperId,
199 'logo' => Arr::get($headerConfig, 'logo', ''),
200 'title' => Arr::get($headerConfig, 'title', ''),
201 'description' => Arr::get($headerConfig, 'description', ''),
202 'wrapper_class' => Arr::get($headerConfig, 'wrapper_class', '')
203 ]);
204 }
205
206 public function handleCalendarShortcode($atts, $content)
207 {
208 $atts = shortcode_atts([
209 'calendar_id' => '',
210 'event_ids' => '',
211 'title' => '',
212 'description' => '',
213 'logo' => '',
214 'hide_info' => false
215 ], $atts);
216
217 $calendarId = intval($atts['calendar_id']);
218 $title = sanitize_text_field($atts['title']);
219 $description = sanitize_text_field($atts['description']);
220 $logo = sanitize_text_field($atts['logo']);
221 $hideInfo = $atts['hide_info'] ? true : false;
222 $eventIds = array_filter(array_map('intval', explode(',', $atts['event_ids'])));
223
224 if (!$calendarId) {
225 return '';
226 }
227
228 $calendar = Calendar::find($calendarId);
229 if (!$calendar) {
230 return '';
231 }
232
233 $calendarEventQuery = CalendarSlot::where('calendar_id', $calendar->id)
234 ->where('status', 'active');
235
236 if ($eventIds && $eventIds != 'all') {
237 $calendarEventQuery->whereIn('id', $eventIds);
238 }
239
240 $calendarEvents = $calendarEventQuery->get();
241
242 if ($calendarEvents->isEmpty()) {
243 return '';
244 }
245
246 $calendarEvents = CalendarEventService::processEvents($calendar, $calendarEvents);
247
248 $calendar->activeEvents = $calendarEvents;
249
250 return $this->renderCalendarBlock($calendar, [
251 'title' => $title,
252 'description' => $description,
253 'logo' => $logo,
254 'hide_info' => $hideInfo,
255 'wrapper_class' => '',
256 ]);
257 }
258
259 public function renderCalendarBlock($calendar, $headerConfig = [])
260 {
261 $wrapperId = 'fcal_team_' . Helper::getNextIndex();
262 wp_enqueue_script('fluent-booking-calendar', App::getInstance('url.assets') . 'public/js/calendar_app.js', [], FLUENT_BOOKING_ASSETS_VERSION, true);
263
264 $calendarHtml = (string)(string)\FluentBooking\App\App::getInstance('view')->make('landing.author_html', [
265 'author' => $calendar->getAuthorProfile(),
266 'calendar' => $calendar,
267 'events' => $calendar->activeEvents,
268 'hideInfo' => Arr::isTrue($headerConfig, 'hide_info'),
269 'block' => true
270 ]);
271
272 $eventCount = count($calendar->activeEvents);
273
274 $vars['fcal_host_calendar' ] = [
275 'calendar_html' => $calendarHtml,
276 'event_count' => $eventCount,
277 'target_event_id' => ($eventCount == 1) ? $calendar->activeEvents[0]->id : 0
278 ];
279
280 foreach ($calendar->activeEvents as $event) {
281 $itemVars = $this->getCalendarEventVars($event->calendar, $event);
282 $extraJs = (new LandingPageHandler())->getEventLandingExtraJsFiles($itemVars['form_fields'], $event);
283 if ($extraJs) {
284 $itemVars['lazy_js_files'] = $extraJs;
285 }
286 wp_localize_script('fluent-booking-calendar', 'fcal_public_vars_' . $event->calendar_id . '_' . $event->id, $itemVars);
287 }
288
289 wp_localize_script('fluent-booking-calendar', $wrapperId, $vars);
290
291 $assetUrl = App::getInstance('url.assets');
292 wp_enqueue_script('fluent-booking-public', $assetUrl . 'public/js/app.js', [], FLUENT_BOOKING_ASSETS_VERSION, true);
293 $this->loadGlobalVars();
294
295 return App::make('view')->make('public.calendar_page', [
296 'calendar' => $calendar,
297 'wrapper_id' => $wrapperId,
298 'logo' => Arr::get($headerConfig, 'logo', ''),
299 'title' => Arr::get($headerConfig, 'title', ''),
300 'description' => Arr::get($headerConfig, 'description', ''),
301 'wrapper_class' => Arr::get($headerConfig, 'wrapper_class', '')
302 ]);
303 }
304
305 public function handleBookingListsShortcode($atts, $content)
306 {
307 $atts = shortcode_atts([
308 'title' => __('My Bookings', 'fluent-booking'),
309 'filter' => 'show',
310 'pagination' => 'show',
311 'period' => 'all',
312 'calendar_ids' => 'all',
313 'no_bookings' => __('No bookings found', 'fluent-booking'),
314 'per_page' => 10
315 ], $atts);
316
317 $atts['title'] = sanitize_text_field($atts['title']);
318 $atts['filter'] = sanitize_text_field($atts['filter']);
319 $atts['pagination'] = sanitize_text_field($atts['pagination']);
320 $atts['no_bookings'] = sanitize_text_field($atts['no_bookings']);
321
322 $userData = get_userdata(get_current_user_id());
323
324 $userEmail = $userData ? $userData->user_email : null;
325
326 if (!$userEmail) {
327 return __('Please login to view your bookings', 'fluent-booking');
328 }
329
330 $data = $_REQUEST; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
331
332 $perPage = intval(Arr::get($data, 'booking_per_page', $atts['per_page']));
333 $currentPage = intval(Arr::get($data, 'booking_page', 1));
334 $bookingPeriod = sanitize_text_field(Arr::get($data, 'booking_period', $atts['period']));
335
336 $bookingQuery = Booking::query()->with('calendar_event')
337 ->where('email', $userEmail)
338 ->orderBy('start_time', 'DESC')
339 ->applyComputedStatus($bookingPeriod);
340
341 if ($atts['calendar_ids'] != 'all') {
342 $atts['calendar_ids'] = array_map('intval', explode(',', $atts['calendar_ids']));
343 $bookingQuery->whereIn('calendar_id', $atts['calendar_ids']);
344 }
345
346 $bookings = $bookingQuery->paginate($perPage, ['*'], 'booking_page', $currentPage)
347 ->appends(['booking_page' => $currentPage])
348 ->withQueryString();
349
350 foreach ($bookings as &$booking) {
351 $booking->author_name = $booking->getHostDetails(false)['name'];
352 $booking->happening_status = $booking->getOngoingStatus();
353 $booking->booking_status_text = $booking->getBookingStatus();
354 $booking->payment_status_text = $booking->getPaymentStatus();
355
356 $booking->booking_date = DateTimeHelper::formatToLocale($booking->getAttendeeStartTime(), 'date');
357 $booking->booking_time = DateTimeHelper::formatToLocale($booking->getAttendeeStartTime(), 'time') . ' - ' . DateTimeHelper::formatToLocale($booking->getAttendeeEndTime(), 'time');
358 }
359
360 $currentPage = $bookings->currentPage();
361 $lastPage = $bookings->lastPage();
362 $startPage = max(1, $currentPage - 2);
363 $endPage = min($lastPage, $currentPage + 2);
364
365 // Adjust if near the beginning or the end
366 if ($currentPage < 3) {
367 $endPage = min($lastPage, 5);
368 }
369 if ($currentPage > $lastPage - 2) {
370 $startPage = max(1, $lastPage - 4);
371 }
372
373 $periodOptions = Helper::getBookingPeriodOptions();
374
375 $pageOptions = apply_filters('fluent_booking/booking_per_page_options', [5, 10, 15, 20, 50, 100]);
376
377 wp_enqueue_script('fluent-booking-list', App::getInstance('url.assets') . 'public/js/bookings.js', [], FLUENT_BOOKING_ASSETS_VERSION, true);
378
379 return App::make('view')->make('public.bookings', [
380 'bookings' => $bookings,
381 'attributes' => $atts,
382 'per_page' => $perPage,
383 'start_page' => $startPage,
384 'end_page' => $endPage,
385 'booking_period' => $bookingPeriod,
386 'page_options' => $pageOptions,
387 'period_options' => $periodOptions
388 ]);
389 }
390
391 public function handleReceiptShortcode($atts, $content)
392 {
393 if (!isset($_REQUEST['hash'])) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
394 return __('Booking hash is missing!', 'fluent-booking');
395 }
396
397 $hash = sanitize_text_field($_REQUEST['hash']); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
398
399 return apply_filters('fluent_booking/payment_receipt_html', '', $hash);
400 }
401
402 public function handleRescheduling($data)
403 {
404 if (empty($data['rescheduling_hash'])) {
405 return;
406 }
407
408 add_filter('fluent_booking/schedule_custom_field_data', function ($array) {
409 return [];
410 });
411
412 add_filter('fluent_booking/schedule_validation_rules_data', function ($data, $postedData, $calendarEvent)
413 {
414 $rules = $messages = [];
415 $rescheduleField = BookingFieldService::getBookingFieldByName($calendarEvent, 'rescheduling_reason');
416
417 if (Arr::isTrue($rescheduleField, 'required')) {
418 $rules['rescheduling_reason'] = 'required';
419 $messages['rescheduling_reason.required'] = __('Please provide a rescheduling reason', 'fluent-booking');
420 }
421
422 return [
423 'rules' => $rules,
424 'messages' => $messages
425 ];
426 }, 10, 3);
427
428 add_action('fluent_booking/before_creating_schedule', function ($bookingData, $postedData, $calendarEvent) {
429 $existingHash = Arr::get($postedData, 'rescheduling_hash');
430 $existingBooking = Booking::where('hash', $existingHash)->first();
431
432 if (!$existingBooking) {
433 wp_send_json([
434 'message' => __('Invalid rescheduling request', 'fluent-booking')
435 ], 422);
436 }
437
438 $rescheduleBy = 'guest';
439 $hostIds = $existingBooking->getHostIds();
440 if (in_array(get_current_user_id(), $hostIds) || PermissionManager::userCan('manage_all_bookings')) {
441 $rescheduleBy = 'host';
442 }
443
444 $existingBooking->updateMeta('rescheduled_by_type', $rescheduleBy);
445
446 if ($rescheduleBy == 'guest' && !$existingBooking->canReschedule()) {
447 wp_send_json([
448 'message' => $existingBooking->getRescheduleMessage()
449 ], 422);
450 }
451
452 if ($bookingData['start_time'] == $existingBooking->start_time) {
453 wp_send_json([
454 'message' => __('Sorry! you can not reschedule to the same time.', 'fluent-booking')
455 ], 422);
456 }
457
458 $endDateTime = gmdate('Y-m-d H:i:s', strtotime($bookingData['start_time']) + ($existingBooking->slot_minutes * 60)); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
459
460 $previousBooking = clone $existingBooking;
461
462 if ($existingBooking->isMultiGuestBooking()) {
463 // Need to handle group booking type here
464 // check for existing group
465 $parent = Booking::where('status', 'scheduled')
466 ->where('event_id', $existingBooking->event_id)
467 ->where('start_time', $bookingData['start_time'])
468 ->orderBy('id', 'ASC')
469 ->first();
470
471 if ($parent) {
472 $existingBooking->group_id = $parent->group_id;
473 } else {
474 $existingBooking->group_id = Helper::getNextBookingGroup();
475 }
476 }
477
478 $existingBooking->start_time = $bookingData['start_time'];
479 $existingBooking->person_time_zone = $bookingData['person_time_zone'];
480 $existingBooking->end_time = $endDateTime;
481 $existingBooking->save();
482
483 $existingBooking->updateMeta('previous_meeting_time', $previousBooking->start_time);
484
485 $reschedulingMessage = sanitize_textarea_field(Arr::get($postedData, 'rescheduling_reason'));
486 if ($reschedulingMessage) {
487 $existingBooking->updateMeta('reschedule_reason', $reschedulingMessage);
488 }
489
490 do_action('fluent_booking/log_booking_activity', [
491 'booking_id' => $existingBooking->id,
492 'type' => 'info',
493 'status' => 'closed',
494 'title' => __('Meeting Rescheduled', 'fluent-booking'),
495 /* translators: %1$s is the user who rescheduled the meeting, %2$s is the previous date and time in UTC. */
496 'description' => sprintf(__('Meeting has been rescheduled by %1$s from Web UI. Previous date time: %2$s (UTC)', 'fluent-booking'), $rescheduleBy, $previousBooking->start_time)
497 ]);
498
499 do_action('fluent_booking/after_booking_rescheduled', $existingBooking, $previousBooking, $calendarEvent);
500
501 add_filter('fluent_booking/schedule_receipt_data', function ($data) {
502 $data['title'] = __('Your meeting has been rescheduled', 'fluent-booking');
503 return $data;
504 });
505
506 $redirectUrl = $existingBooking->getRedirectUrlWithQuery();
507
508 $html = BookingService::getBookingConfirmationHtml($existingBooking);
509
510 wp_send_json(apply_filters('fluent_booking/booking_rescheduled_response', [
511 'message' => __('Booking has been rescheduled', 'fluent-booking'),
512 'redirect_url' => $redirectUrl,
513 'response_html' => $html,
514 'booking_hash' => $existingBooking->hash
515 ], $existingBooking), 200);
516
517 }, 10, 3);
518 }
519
520 private function loadGlobalVars()
521 {
522 static $loaded;
523
524 if ($loaded) {
525 return;
526 }
527
528 $loaded = true;
529
530 wp_localize_script('fluent-booking-public', 'fluentCalendarPublicVars', $this->getGlobalVars());
531 }
532
533 public function getGlobalVars()
534 {
535 $currentPerson = [
536 'name' => '',
537 'email' => ''
538 ];
539
540 if (is_user_logged_in()) {
541 $currentUser = wp_get_current_user();
542 $name = trim($currentUser->first_name . ' ' . $currentUser->last_name);
543
544 if (!$name) {
545 $name = $currentUser->display_name;
546 }
547
548 $currentPerson = [
549 'name' => $name,
550 'email' => $currentUser->user_email,
551 'user_id' => $currentUser->ID
552 ];
553 } else {
554 $request = $_REQUEST; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
555
556 // Check for url params
557 if ($name = sanitize_text_field(Arr::get($request, 'invitee_name'))) {
558 $currentPerson['name'] = $name;
559 }
560
561 if ($email = sanitize_email(Arr::get($request, 'invitee_email'))) {
562 if (is_email($email)) {
563 $currentPerson['email'] = $email;
564 }
565 }
566 }
567
568 if (empty($currentPerson['email'])) {
569 // Let's try to get from FluentCRM is exists
570 if (defined('FLUENTCRM')) {
571 $contactApi = FluentCrmApi('contacts');
572 $contact = $contactApi->getCurrentContact();
573 if ($contact) {
574 $currentPerson['email'] = $contact->email;
575 $currentPerson['name'] = $contact->full_name;
576 }
577 }
578 }
579
580 $globalSettings = Helper::getGlobalSettings();
581 $startDay = Arr::get($globalSettings, 'administration.start_day', 'mon');
582
583 $data = [
584 'ajaxurl' => admin_url('admin-ajax.php'),
585 'timezones' => DateTimeHelper::getFlatGroupedTimeZones(),
586 'current_person' => $currentPerson,
587 'start_day' => $startDay,
588 'i18' => [
589 'Timezone' => __('Timezone', 'fluent-booking'),
590 'Day' => __('Day', 'fluent-booking'),
591 'Days' => __('Days', 'fluent-booking'),
592 'Hour' => __('Hour', 'fluent-booking'),
593 'Hours' => __('Hours', 'fluent-booking'),
594 'Minute' => __('Minute', 'fluent-booking'),
595 'Minutes' => __('Minutes', 'fluent-booking'),
596 'Enter Details' => __('Enter Details', 'fluent-booking'),
597 'Summary' => __('Summary', 'fluent-booking'),
598 'Payment Details' => __('Payment Details', 'fluent-booking'),
599 'Total Payment' => __('Total Payment', 'fluent-booking'),
600 'Payment Method' => __('Payment Method', 'fluent-booking'),
601 'Pay Now' => __('Pay Now', 'fluent-booking'),
602 'processing' => __('Processing', 'fluent-booking'),
603 'date_time_config' => [
604 'weekdays' => array(
605 'sunday' => _x('Sunday', 'calendar day full', 'fluent-booking'),
606 'monday' => _x('Monday', 'calendar day full', 'fluent-booking'),
607 'tuesday' => _x('Tuesday', 'calendar day full', 'fluent-booking'),
608 'wednesday' => _x('Wednesday', 'calendar day full', 'fluent-booking'),
609 'thursday' => _x('Thursday', 'calendar day full', 'fluent-booking'),
610 'friday' => _x('Friday', 'calendar day full', 'fluent-booking'),
611 'saturday' => _x('Saturday', 'calendar day full', 'fluent-booking'),
612 ),
613 'months' => array(
614 'January' => _x('January', 'calendar month name full', 'fluent-booking'),
615 'February' => _x('February', 'calendar month name full', 'fluent-booking'),
616 'March' => _x('March', 'calendar month name full', 'fluent-booking'),
617 'April' => _x('April', 'calendar month name full', 'fluent-booking'),
618 'May' => _x('May', 'calendar month name full', 'fluent-booking'),
619 'June' => _x('June', 'calendar month name full', 'fluent-booking'),
620 'July' => _x('July', 'calendar month name full', 'fluent-booking'),
621 'August' => _x('August', 'calendar month name full', 'fluent-booking'),
622 'September' => _x('September', 'calendar month name full', 'fluent-booking'),
623 'October' => _x('October', 'calendar month name full', 'fluent-booking'),
624 'November' => _x('November', 'calendar month name full', 'fluent-booking'),
625 'December' => _x('December', 'calendar month name full', 'fluent-booking')
626 ),
627 'weekdaysShort' => array(
628 'sun' => _x('Sun', 'calendar day short', 'fluent-booking'),
629 'mon' => _x('Mon', 'calendar day short', 'fluent-booking'),
630 'tue' => _x('Tue', 'calendar day short', 'fluent-booking'),
631 'wed' => _x('Wed', 'calendar day short', 'fluent-booking'),
632 'thu' => _x('Thu', 'calendar day short', 'fluent-booking'),
633 'fri' => _x('Fri', 'calendar day short', 'fluent-booking'),
634 'sat' => _x('Sat', 'calendar day short', 'fluent-booking')
635 ),
636 'monthsShort' => array(
637 'jan' => _x('Jan', 'calendar month name short', 'fluent-booking'),
638 'feb' => _x('Feb', 'calendar month name short', 'fluent-booking'),
639 'mar' => _x('Mar', 'calendar month name short', 'fluent-booking'),
640 'apr' => _x('Apr', 'calendar month name short', 'fluent-booking'),
641 'may' => _x('May', 'calendar month name short', 'fluent-booking'),
642 'jun' => _x('Jun', 'calendar month name short', 'fluent-booking'),
643 'jul' => _x('Jul', 'calendar month name short', 'fluent-booking'),
644 'aug' => _x('Aug', 'calendar month name short', 'fluent-booking'),
645 'sep' => _x('Sep', 'calendar month name short', 'fluent-booking'),
646 'oct' => _x('Oct', 'calendar month name short', 'fluent-booking'),
647 'nov' => _x('Nov', 'calendar month name short', 'fluent-booking'),
648 'dec' => _x('Dec', 'calendar month name short', 'fluent-booking')
649 ),
650 'numericSystem' => _x('0_1_2_3_4_5_6_7_8_9', 'calendar numeric system - Sequence must need to maintained', 'fluent-booking'),
651 ],
652 'Country' => __('Country', 'fluent-booking'),
653 '12h' => _x('12h', 'date time format switch', 'fluent-booking'),
654 '24h' => _x('24h', 'date time format switch', 'fluent-booking'),
655 'spots left' => _x('spots left', 'for how many spots left for available booking', 'fluent-booking'),
656 'spots remaining' => _x('spots remaining', 'for how many spots remaining for available booking', 'fluent-booking'),
657 'Next' => _x('Next', 'Booking form spot selection', 'fluent-booking'),
658 'Select on the Next Step' => __('Select on the Next Step', 'fluent-booking'),
659 'location options' => __('location options', 'fluent-booking'),
660 'Your address' => __('Your address', 'fluent-booking'),
661 'Organizer Phone Number' => __('Organizer Phone Number', 'fluent-booking'),
662 'In Person (Attendee Address)' => __('In Person (Attendee Address)', 'fluent-booking'),
663 'In Person (Organizer Address)' => __('In Person (Organizer Address)', 'fluent-booking'),
664 'Attendee Phone Number' => __('Attendee Phone Number', 'fluent-booking'),
665 'Google Meet' => __('Google Meet', 'fluent-booking'),
666 'Zoom Meeting' => __('Zoom Meeting', 'fluent-booking'),
667 'Online Meeting' => __('Online Meeting', 'fluent-booking'),
668 'Phone Call' => __('Phone Call', 'fluent-booking'),
669 'Processing...' => __('Processing...', 'fluent-booking'),
670 'Loading Payment Processor...' => __('Loading Payment Processor...', 'fluent-booking'),
671 'PM' => __('PM', 'fluent-booking'),
672 'AM' => __('AM', 'fluent-booking'),
673 'Email' => __('Email', 'fluent-booking'),
674 'Date' => __('Date', 'fluent-booking'),
675 'Time' => __('Time', 'fluent-booking'),
676 'per guest' => __('per guest', 'fluent-booking'),
677 'Add guest' => __('Add guest', 'fluent-booking'),
678 'Add guests' => __('Add guests', 'fluent-booking'),
679 'Add another' => __('Add another', 'fluent-booking'),
680 'This field is required.' => __('This field is required.', 'fluent-booking'),
681 'No availability in' => __('No availability in', 'fluent-booking'),
682 'View next month' => __('View next month', 'fluent-booking'),
683 'View previous month' => __('View previous month', 'fluent-booking'),
684 'No_payment_method_description' => __('No activated payment method found. If you are an admin please check the event payment settings', 'fluent-booking'),
685 'Please fill up the required data' => __('Please fill up the required data', 'fluent-booking'),
686 'Please select a valid payment method' => __('Please select a valid payment method', 'fluent-booking'),
687 'Please Select' => __('Please Select', 'fluent-booking'),
688 'Something is wrong!' => __('Something is wrong!', 'fluent-booking'),
689 'Requires Confirmation' => __('Requires Confirmation', 'fluent-booking'),
690 ],
691 'theme' => Arr::get(get_option('_fluent_booking_settings'), 'theme','system-default')
692 ];
693
694 if (isset($_SERVER['HTTP_CF_IPCOUNTRY'])) {
695 $data['user_country'] = sanitize_text_field($_SERVER['HTTP_CF_IPCOUNTRY']); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
696 } else {
697 $data['user_country'] = Arr::get($globalSettings, 'administration.default_country', '');
698 }
699
700 return apply_filters('fluent_calendar/global_booking_vars', $data);
701 }
702
703 public function ajaxScheduleMeeting()
704 {
705 $app = App::getInstance();
706
707 $postedData = $_REQUEST; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
708
709 $eventId = (int)$postedData['event_id'];
710
711 $isRescheduling = Arr::get($postedData, 'rescheduling_hash', '');
712
713 $calendarEvent = CalendarSlot::find($eventId);
714
715 if (!$calendarEvent || ($calendarEvent->status != 'active' && !$isRescheduling)) {
716 wp_send_json([
717 'message' => __('Sorry, the host is not accepting any new bookings at the moment.', 'fluent-booking')
718 ], 422);
719 }
720
721 do_action('fluent_booking/starting_scheduling_ajax', $postedData);
722
723 $rules = [
724 'name' => 'required',
725 'email' => 'required|email',
726 'timezone' => 'required',
727 'start_date' => 'required'
728 ];
729
730 $messages = [
731 'name.required' => __('Please enter your name', 'fluent-booking'),
732 'email.required' => __('Please enter your email address', 'fluent-booking'),
733 'email.email' => __('Please enter provide a valid email address', 'fluent-booking'),
734 'timezone.required' => __('Please select timezone first', 'fluent-booking'),
735 'start_date.required' => __('Please select a date and time', 'fluent-booking')
736 ];
737
738 if ($calendarEvent->isPhoneRequired()) {
739 $rules['phone_number'] = 'required';
740 $messages['phone_number.required'] = __('Please provide your phone number', 'fluent-booking');
741 } else if ($calendarEvent->isAddressRequired()) {
742 $rules['address'] = 'required';
743 $messages['address.required'] = __('Please provide your Address', 'fluent-booking');
744 } else if ($calendarEvent->isLocationFieldRequired()) {
745 $rules['location_config.driver'] = 'required';
746 $messages['location_config.driver'] = __('Please select location', 'fluent-booking');
747
748 $selectedLocation = LocationService::getLocationDetails($calendarEvent, Arr::get($postedData, 'location_config', []), $postedData);
749 $selectedLocationDriver = Arr::get($selectedLocation, 'type');
750 // is user input required
751 if (in_array($selectedLocationDriver, ['in_person_guest', 'phone_guest'])) {
752 $rules['location_config.user_location_input'] = 'required';
753 if ($selectedLocationDriver == 'in_person_guest') {
754 $messages['location_config.user_location_input.required'] = __('Please provide your address', 'fluent-booking');
755 } else {
756 $messages['location_config.user_location_input.required'] = __('Please provide your phone number', 'fluent-booking');
757 }
758 }
759 }
760
761 $duration = (int)$calendarEvent->getDuration(Arr::get($postedData, 'duration', null));
762
763 if ($calendarEvent->isPaymentEnabled($duration)) {
764 $rules['payment_method'] = 'required';
765 $messages['payment_method.required'] = __('Please select a valid payment method', 'fluent-booking');
766 }
767
768 if ($additionalGuests = Arr::get($postedData, 'guests', [])) {
769 if (in_array($calendarEvent->event_type, ['group', 'group_event'])) {
770 $additionalGuests = $this->sanitize_mapped_data($additionalGuests);
771 $additionalGuests = array_values(array_filter($additionalGuests, function ($guest) {
772 return Arr::get($guest, 'name') && Arr::get($guest, 'email');
773 }));
774 } else {
775 $additionalGuests = array_filter(array_map('sanitize_email', $additionalGuests));
776 }
777 }
778
779 $postedData['guests'] = $additionalGuests;
780
781 $requiredFields = array_filter($calendarEvent->getMeta('booking_fields', []), function ($field) {
782 return Arr::isTrue($field, 'required') && Arr::isTrue($field, 'enabled') && (Arr::get($field, 'name') == 'message' || Arr::get($field, 'name') == 'guests');
783 });
784
785 foreach ($requiredFields as $field) {
786 if (empty($rules[$field['name']])) {
787 $rules[$field['name']] = 'required';
788 $messages[$field['name'] . '.required'] = __('This field is required', 'fluent-booking');
789 }
790 }
791
792 $validationConfig = apply_filters('fluent_booking/schedule_validation_rules_data', [
793 'rules' => $rules,
794 'messages' => $messages
795 ], $postedData, $calendarEvent);
796
797 $validator = $app->validator->make($postedData, $validationConfig['rules'], $validationConfig['messages']);
798 if ($validator->validate()->fails()) {
799 wp_send_json([
800 'message' => __('Please fill up the required data', 'fluent-booking'),
801 'errors' => $validator->errors()
802 ], 422);
803 return;
804 }
805
806 $customFieldsData = BookingFieldService::getCustomFieldsData($postedData, $calendarEvent);
807 $customFieldsData = apply_filters('fluent_booking/schedule_custom_field_data', $customFieldsData, $customFieldsData, $calendarEvent);
808
809 if (is_wp_error($customFieldsData)) {
810 wp_send_json([
811 'message' => $customFieldsData->get_error_message(),
812 'errors' => $customFieldsData->get_error_data()
813 ], 422);
814 return;
815 }
816
817 $startDate = Arr::get($postedData, 'start_date');
818 $timezone = sanitize_text_field(Arr::get($postedData, 'timezone', 'UTC'));
819
820 if (is_array($startDate)) {
821 $startDateTime = array_slice(
822 array_map(function($date) use ($timezone) {
823 return DateTimeHelper::convertToUtc(sanitize_text_field($date), $timezone);
824 }, $startDate), 0, $calendarEvent->multiBookingLimit()
825 );
826 $endDateTime = array_map(function($date) use ($duration) {
827 return gmdate('Y-m-d H:i:s', strtotime($date) + ($duration * 60)); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
828 }, $startDateTime);
829 }
830
831 if (is_string($startDate)) {
832 $startDate = sanitize_text_field($startDate);
833 $startDateTime = DateTimeHelper::convertToUtc($startDate, $timezone);
834 $endDateTime = gmdate('Y-m-d H:i:s', strtotime($startDateTime) + ($duration * 60)); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
835 }
836
837 $bookingData = [
838 'person_time_zone' => sanitize_text_field($timezone),
839 'start_time' => $startDateTime,
840 'end_time' => $endDateTime,
841 'name' => sanitize_text_field($postedData['name']),
842 'email' => sanitize_email($postedData['email']),
843 'message' => sanitize_textarea_field(wp_unslash(Arr::get($postedData, 'message', ''))),
844 'phone' => sanitize_textarea_field(Arr::get($postedData, 'phone_number', '')),
845 'address' => sanitize_textarea_field(Arr::get($postedData, 'address', '')),
846 'ip_address' => Helper::getIp(),
847 'status' => 'scheduled',
848 'source' => 'web',
849 'event_type' => $calendarEvent->event_type,
850 'slot_minutes' => $duration
851 ];
852
853 if ($calendarEvent->isConfirmationRequired($startDateTime)) {
854 $bookingData['status'] = 'pending';
855 }
856
857 $locationConfig = Arr::get($postedData, 'location_config', []);
858 $selectedLocation = LocationService::getLocationDetails($calendarEvent, $locationConfig, $postedData);
859 if ($selectedLocation['type'] == 'phone_guest') {
860 $bookingData['phone'] = $selectedLocation['description'];
861 }
862
863 $bookingData['location_details'] = $selectedLocation;
864
865 if ($sourceUrl = Arr::get($postedData, 'source_url', '')) {
866 $bookingData['source_url'] = sanitize_url($sourceUrl);
867 }
868
869 if (!empty($postedData['payment_method'])) {
870 $customFieldsData['payment_method'] = $postedData['payment_method'];
871 }
872
873 $timeSlotService = TimeSlotServiceHandler::initService($calendarEvent->calendar, $calendarEvent);
874
875 if (is_wp_error($timeSlotService)) {
876 return TimeSlotServiceHandler::sendError($timeSlotService, $calendarEvent, $timezone);
877 }
878
879 $availableSpot = $timeSlotService->isSpotAvailable($startDateTime, $endDateTime, $duration);
880
881 if (!$availableSpot) {
882 wp_send_json([
883 'message' => __('This selected time slot is not available. Maybe someone booked the spot just a few seconds ago.', 'fluent-booking')
884 ], 422);
885 }
886
887 if ($additionalGuests) {
888 $guestField = BookingFieldService::getBookingFieldByName($calendarEvent, 'guests');
889 $guestLimit = Arr::get($guestField, 'limit', 10);
890 if ($calendarEvent->isMultiGuestEvent()) {
891 $remaining = Arr::get($availableSpot, 'remaining', $calendarEvent->getMaxBookingPerSlot());
892 $guestLimit = min($remaining, $guestLimit) - 1;
893 }
894 $bookingData['additional_guests'] = array_slice($additionalGuests, 0, $guestLimit);
895 }
896
897 if ($calendarEvent->isTeamEvent()) {
898 $bookingData['host_user_id'] = $timeSlotService->hostUserId;
899 }
900
901 do_action('fluent_booking/before_creating_schedule', $bookingData, $postedData, $calendarEvent);
902
903 try {
904 $booking = BookingService::createBooking($bookingData, $calendarEvent, $customFieldsData);
905
906 if (is_wp_error($booking)) {
907 throw new \Exception(wp_kses_post($booking->get_error_message()), 422);
908 }
909
910 } catch (\Exception $e) {
911 wp_send_json([
912 'message' => $e->getMessage()
913 ], 422);
914 return;
915 }
916
917 $redirectUrl = $booking->getRedirectUrlWithQuery();
918
919 $html = BookingService::getBookingConfirmationHtml($booking);
920
921 wp_send_json(apply_filters('fluent_booking/booking_confirmation_response', [
922 'message' => __('Booking has been confirmed', 'fluent-booking'),
923 'redirect_url' => $redirectUrl,
924 'response_html' => $html,
925 'booking_hash' => $booking->hash
926 ], $booking), 200);
927 }
928
929 public function ajaxGetAvailableDates()
930 {
931 $startBenchmark = microtime(true);
932
933 $request = $_REQUEST; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
934
935 $eventId = (int)$request['event_id'];
936
937 $rescheduling = Arr::get($request, 'rescheduling', 'no');
938
939 $calendarEvent = CalendarSlot::findOrfail($eventId);
940
941 if (!$calendarEvent || ($calendarEvent->status != 'active' && $rescheduling == 'no')) {
942 wp_send_json([
943 'message' => __('Sorry, the host is not accepting any new bookings at the moment.', 'fluent-booking')
944 ], 422);
945 }
946
947 $calendar = $calendarEvent->calendar;
948 $startDate = sanitize_text_field(Arr::get($request, 'start_date')); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
949
950 if (!$startDate) {
951 $startDate = gmdate('Y-m-d H:i:s'); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
952 }
953
954 $timeZone = sanitize_text_field(Arr::get($request, 'timezone')); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
955
956 if (!$timeZone) {
957 $timeZone = wp_timezone_string();
958 }
959
960 if (!in_array($timeZone, \DateTimeZone::listIdentifiers())) {
961 $timeZone = $calendar->author_timezone;
962 }
963
964 $duration = (int)$calendarEvent->getDuration(Arr::get($request, 'duration', null));
965
966 $timeSlotService = TimeSlotServiceHandler::initService($calendar, $calendarEvent);
967
968 if (is_wp_error($timeSlotService)) {
969 return TimeSlotServiceHandler::sendError($timeSlotService, $calendarEvent, $timeZone);
970 }
971
972 $availableSpots = $timeSlotService->getAvailableSpots($startDate, $timeZone, $duration);
973
974 if (is_wp_error($availableSpots)) {
975 return TimeSlotServiceHandler::sendError($availableSpots, $calendarEvent, $timeZone);
976 }
977
978 $availableSpots = array_filter($availableSpots);
979 $availableSpots = apply_filters('fluent_booking/available_slots_for_view', $availableSpots, $calendarEvent, $calendar, $timeZone, $duration);
980
981 wp_send_json([
982 'available_slots' => $availableSpots,
983 'timezone' => $timeZone,
984 'max_lookup_date' => $calendarEvent->getMaxLookUpDate(),
985 'execution_time' => microtime(true) - $startBenchmark
986 ], 200);
987 }
988
989 public function getCalendarEventVars(Calendar $calendar, CalendarSlot $calendarEvent)
990 {
991 $calendarEvent->description = wpautop($calendarEvent->description);
992 $calendarEvent->location_icon_html = $calendarEvent->defaultLocationHtml();
993 $formFields = BookingFieldService::getBookingFields($calendarEvent);
994
995 $eventData = [
996 'id' => $calendarEvent->id,
997 'max_lookup_date' => $calendarEvent->getMaxLookUpDate(),
998 'min_lookup_date' => $calendarEvent->getMinLookUpDate(),
999 'min_bookable_date' => $calendarEvent->getMinBookableDateTime(),
1000 'is_display_spots' => $calendarEvent->isDisplaySpots(),
1001 'duration' => $calendarEvent->getDefaultDuration(),
1002 'title' => $calendarEvent->title,
1003 'location_settings' => $calendarEvent->location_settings,
1004 'location_icon_html' => $calendarEvent->location_icon_html,
1005 'description' => $calendarEvent->description,
1006 'pre_selects' => null,
1007 'settings' => $calendarEvent->settings,
1008 'type' => $calendarEvent->type,
1009 'event_type' => $calendarEvent->event_type,
1010 'time_format' => Arr::get(get_option('_fluent_booking_settings'), 'time_format', '12'),
1011 ];
1012
1013 $author = $calendar->getAuthorProfile(true);
1014 $author['name'] = $calendar->title;
1015
1016 $eventVars = [
1017 'slot' => $eventData,
1018 'author_profile' => $author,
1019 'form_fields' => $formFields,
1020 'i18n' => [
1021 'Schedule_Meeting' => __('Schedule Meeting', 'fluent-booking'),
1022 'Continue_to_Payments' => __('Continue to Payments', 'fluent-booking'),
1023 'Confirm_Payment' => __('Confirm Payment', 'fluent-booking'),
1024 ],
1025 'date_formatter' => DateTimeHelper::getDateFormatter(true),
1026 'isRtl' => Helper::fluentbooking_is_rtl(),
1027 'duration_lookup' => Helper::getDurationLookup(),
1028 'multi_duration_lookup' => Helper::getDurationLookup(true)
1029 ];
1030
1031 $eventVars['form_fields'] = array_values($eventVars['form_fields']);
1032
1033 if (!$calendar->isHostCalendar()) {
1034 $eventVars['team_member_profiles'] = $calendarEvent->getAuthorProfiles(true);
1035 }
1036
1037 return apply_filters('fluent_booking/public_event_vars', $eventVars, $calendarEvent);
1038 }
1039
1040 public function ajaxHandleCancelMeeting()
1041 {
1042 $data = $_REQUEST; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
1043
1044 $meetingHash = Arr::get($data, 'meeting_hash');
1045
1046 $meeting = Booking::where('hash', $meetingHash)->first();
1047
1048 if (!$meeting) {
1049 wp_send_json([
1050 'message' => __('Sorry! meeting could not be found', 'fluent-booking')
1051 ], 422);
1052 }
1053
1054 if (!$meeting->canCancel()) {
1055 wp_send_json([
1056 'message' => $meeting->getCancellationMessage()
1057 ], 422);
1058 }
1059
1060 $message = sanitize_textarea_field(Arr::get($data, 'cancellation_reason', ''));
1061
1062 $cancelField = BookingFieldService::getBookingFieldByName($meeting->calendar_event, 'cancellation_reason');
1063
1064 if (!$message && Arr::isTrue($cancelField, 'required')) {
1065 wp_send_json([
1066 'message' => __('Please provide a reason for cancellation', 'fluent-booking')
1067 ], 422);
1068 }
1069
1070 $result = $meeting->cancelMeeting($message, 'guest', get_current_user_id());
1071
1072 if (is_wp_error($result)) {
1073 if (!wp_doing_ajax()) {
1074 wp_redirect($meeting->getConfirmationUrl());
1075 exit();
1076 }
1077
1078 wp_send_json([
1079 'message' => $result->get_error_message()
1080 ], 422);
1081 }
1082
1083 if (wp_doing_ajax()) {
1084 wp_send_json([
1085 'message' => __('Meeting has been cancelled', 'fluent-booking')
1086 ], 200);
1087 }
1088
1089 wp_redirect($meeting->getConfirmationUrl());
1090 exit;
1091 }
1092
1093 private static function sanitize_mapped_data($settings)
1094 {
1095 $sanitizerMap = [
1096 'name' => 'sanitize_text_field',
1097 'email' => 'sanitize_email',
1098 ];
1099
1100 return Helper::fcal_backend_sanitizer($settings, $sanitizerMap);
1101 }
1102 }
1103