PluginProbe
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution / 2.2.0
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution v2.2.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 1.7.2 All 33 releases
fluent-booking / app / Services / Helper.php

Helper.php in Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution 2.2.0, at app/Services/Helper.php

2,390 lines 87.3 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\Services;
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\Models\Meta;
10 use FluentBooking\App\Models\BookingMeta;
11 use FluentBooking\Framework\Support\Arr;
12
13 class Helper
14 {
15 public static $reserved = [
16 '0',
17 'about',
18 'access',
19 'account',
20 'accounts',
21 'activate',
22 'activities',
23 'activity',
24 'ad',
25 'add',
26 'address',
27 'adm',
28 'admin',
29 'administration',
30 'administrator',
31 'ads',
32 'adult',
33 'advertising',
34 'affiliate',
35 'affiliates',
36 'ajax',
37 'all',
38 'alpha',
39 'analysis',
40 'analytics',
41 'android',
42 'anon',
43 'anonymous',
44 'api',
45 'app',
46 'apps',
47 'archive',
48 'archives',
49 'article',
50 'asct',
51 'asset',
52 'atom',
53 'auth',
54 'authentication',
55 'avatar',
56 'backup',
57 'balancer-manager',
58 'banner',
59 'banners',
60 'beta',
61 'billing',
62 'bin',
63 'blog',
64 'blogs',
65 'board',
66 'book',
67 'bookmark',
68 'bot',
69 'bots',
70 'bug',
71 'business',
72 'cache',
73 'cadastro',
74 'calendar',
75 'call',
76 'campaign',
77 'cancel',
78 'captcha',
79 'career',
80 'careers',
81 'cart',
82 'categories',
83 'category',
84 'cgi',
85 'cgi-bin',
86 'changelog',
87 'chat',
88 'check',
89 'checking',
90 'checkout',
91 'client',
92 'cliente',
93 'clients',
94 'code',
95 'codereview',
96 'comercial',
97 'comment',
98 'comments',
99 'communities',
100 'community',
101 'company',
102 'compare',
103 'compras',
104 'config',
105 'configuration',
106 'connect',
107 'contact',
108 'contact-us',
109 'contact_us',
110 'contactus',
111 'contest',
112 'contribute',
113 'corp',
114 'create',
115 'css',
116 'dashboard',
117 'data',
118 'db',
119 'default',
120 'delete',
121 'demo',
122 'design',
123 'designer',
124 'destroy',
125 'dev',
126 'devel',
127 'developer',
128 'developers',
129 'diagram',
130 'diary',
131 'dict',
132 'dictionary',
133 'die',
134 'dir',
135 'direct_messages',
136 'directory',
137 'dist',
138 'doc',
139 'docs',
140 'documentation',
141 'domain',
142 'download',
143 'downloads',
144 'ecommerce',
145 'edit',
146 'editor',
147 'edu',
148 'education',
149 'email',
150 'employment',
151 'empty',
152 'end',
153 'enterprise',
154 'entries',
155 'entry',
156 'error',
157 'errors',
158 'eval',
159 'event',
160 'exit',
161 'explore',
162 'facebook',
163 'faq',
164 'favorite',
165 'favorites',
166 'feature',
167 'features',
168 'feed',
169 'feedback',
170 'feeds',
171 'file',
172 'files',
173 'first',
174 'flash',
175 'fleet',
176 'fleets',
177 'flog',
178 'follow',
179 'followers',
180 'following',
181 'forgot',
182 'form',
183 'forum',
184 'forums',
185 'founder',
186 'free',
187 'friend',
188 'friends',
189 'ftp',
190 'gadget',
191 'gadgets',
192 'game',
193 'games',
194 'get',
195 'ghost',
196 'gift',
197 'gifts',
198 'gist',
199 'github',
200 'graph',
201 'group',
202 'groups',
203 'guest',
204 'guests',
205 'help',
206 'home',
207 'homepage',
208 'host',
209 'hosting',
210 'hostmaster',
211 'hostname',
212 'howto',
213 'hpg',
214 'html',
215 'http',
216 'httpd',
217 'https',
218 'i',
219 'iamges',
220 'icon',
221 'icons',
222 'id',
223 'idea',
224 'ideas',
225 'image',
226 'images',
227 'imap',
228 'img',
229 'index',
230 'indice',
231 'info',
232 'information',
233 'inquiry',
234 'instagram',
235 'intranet',
236 'invitations',
237 'invite',
238 'ipad',
239 'iphone',
240 'irc',
241 'is',
242 'issue',
243 'issues',
244 'it',
245 'item',
246 'items',
247 'java',
248 'javascript',
249 'job',
250 'jobs',
251 'join',
252 'js',
253 'json',
254 'jump',
255 'knowledgebase',
256 'language',
257 'languages',
258 'last',
259 'ldap-status',
260 'legal',
261 'license',
262 'link',
263 'links',
264 'linux',
265 'list',
266 'lists',
267 'log',
268 'log-in',
269 'log-out',
270 'log_in',
271 'log_out',
272 'login',
273 'logout',
274 'logs',
275 'm',
276 'mac',
277 'mail',
278 'mail1',
279 'mail2',
280 'mail3',
281 'mail4',
282 'mail5',
283 'mailer',
284 'mailing',
285 'maintenance',
286 'manager',
287 'manual',
288 'map',
289 'maps',
290 'marketing',
291 'master',
292 'me',
293 'media',
294 'member',
295 'members',
296 'message',
297 'messages',
298 'messenger',
299 'microblog',
300 'microblogs',
301 'mine',
302 'mis',
303 'mob',
304 'mobile',
305 'movie',
306 'movies',
307 'mp3',
308 'msg',
309 'msn',
310 'music',
311 'musicas',
312 'mx',
313 'my',
314 'mysql',
315 'name',
316 'named',
317 'nan',
318 'navi',
319 'navigation',
320 'net',
321 'network',
322 'new',
323 'news',
324 'newsletter',
325 'nick',
326 'nickname',
327 'notes',
328 'noticias',
329 'notification',
330 'notifications',
331 'notify',
332 'ns',
333 'ns1',
334 'ns10',
335 'ns2',
336 'ns3',
337 'ns4',
338 'ns5',
339 'ns6',
340 'ns7',
341 'ns8',
342 'ns9',
343 'null',
344 'oauth',
345 'oauth_clients',
346 'offer',
347 'offers',
348 'official',
349 'old',
350 'online',
351 'openid',
352 'operator',
353 'order',
354 'orders',
355 'organization',
356 'organizations',
357 'overview',
358 'owner',
359 'owners',
360 'page',
361 'pager',
362 'pages',
363 'panel',
364 'password',
365 'payment',
366 'perl',
367 'phone',
368 'photo',
369 'photoalbum',
370 'photos',
371 'php',
372 'phpmyadmin',
373 'phppgadmin',
374 'phpredisadmin',
375 'pic',
376 'pics',
377 'ping',
378 'plan',
379 'plans',
380 'plugin',
381 'plugins',
382 'policy',
383 'pop',
384 'pop3',
385 'popular',
386 'portal',
387 'post',
388 'postfix',
389 'postmaster',
390 'posts',
391 'pr',
392 'premium',
393 'press',
394 'price',
395 'pricing',
396 'privacy',
397 'privacy-policy',
398 'privacy_policy',
399 'privacypolicy',
400 'private',
401 'product',
402 'products',
403 'profile',
404 'project',
405 'projects',
406 'promo',
407 'pub',
408 'public',
409 'purpose',
410 'put',
411 'python',
412 'query',
413 'random',
414 'ranking',
415 'read',
416 'readme',
417 'recent',
418 'recruit',
419 'recruitment',
420 'register',
421 'registration',
422 'release',
423 'remove',
424 'replies',
425 'report',
426 'reports',
427 'repositories',
428 'repository',
429 'req',
430 'request',
431 'requests',
432 'reset',
433 'roc',
434 'rss',
435 'ruby',
436 'rule',
437 'sag',
438 'sale',
439 'sales',
440 'sample',
441 'samples',
442 'save',
443 'school',
444 'script',
445 'scripts',
446 'search',
447 'secure',
448 'security',
449 'self',
450 'send',
451 'server',
452 'server-info',
453 'server-status',
454 'service',
455 'services',
456 'session',
457 'sessions',
458 'setting',
459 'settings',
460 'setup',
461 'share',
462 'shop',
463 'show',
464 'sign-in',
465 'sign-up',
466 'sign_in',
467 'sign_up',
468 'signin',
469 'signout',
470 'signup',
471 'site',
472 'sitemap',
473 'sites',
474 'smartphone',
475 'smtp',
476 'soporte',
477 'source',
478 'spec',
479 'special',
480 'sql',
481 'src',
482 'ssh',
483 'ssl',
484 'ssladmin',
485 'ssladministrator',
486 'sslwebmaster',
487 'staff',
488 'stage',
489 'staging',
490 'start',
491 'stat',
492 'state',
493 'static',
494 'stats',
495 'status',
496 'store',
497 'stores',
498 'stories',
499 'style',
500 'styleguide',
501 'stylesheet',
502 'stylesheets',
503 'subdomain',
504 'subscribe',
505 'subscriptions',
506 'suporte',
507 'support',
508 'svn',
509 'swf',
510 'sys',
511 'sysadmin',
512 'sysadministrator',
513 'system',
514 'tablet',
515 'tablets',
516 'tag',
517 'talk',
518 'task',
519 'tasks',
520 'team',
521 'teams',
522 'tech',
523 'telnet',
524 'term',
525 'terms',
526 'terms-of-service',
527 'terms_of_service',
528 'termsofservice',
529 'test',
530 'test1',
531 'test2',
532 'test3',
533 'teste',
534 'testing',
535 'tests',
536 'theme',
537 'themes',
538 'thread',
539 'threads',
540 'tmp',
541 'todo',
542 'tool',
543 'tools',
544 'top',
545 'topic',
546 'topics',
547 'tos',
548 'tour',
549 'translations',
550 'trends',
551 'tutorial',
552 'tux',
553 'tv',
554 'twitter',
555 'undef',
556 'unfollow',
557 'unsubscribe',
558 'update',
559 'upload',
560 'uploads',
561 'url',
562 'usage',
563 'user',
564 'username',
565 'users',
566 'usuario',
567 'vendas',
568 'ver',
569 'version',
570 'video',
571 'videos',
572 'visitor',
573 'watch',
574 'weather',
575 'web',
576 'webhook',
577 'webhooks',
578 'webmail',
579 'webmaster',
580 'website',
581 'websites',
582 'welcome',
583 'widget',
584 'widgets',
585 'wiki',
586 'win',
587 'windows',
588 'word',
589 'work',
590 'works',
591 'workshop',
592 'ww',
593 'wws',
594 'www',
595 'www1',
596 'www2',
597 'www3',
598 'www4',
599 'www5',
600 'www6',
601 'www7',
602 'wwws',
603 'wwww',
604 'xfn',
605 'xml',
606 'xmpp',
607 'xpg',
608 'xxx',
609 'yaml',
610 'year',
611 'yml',
612 'you',
613 'yourdomain',
614 'yourname',
615 'yoursite',
616 'yourusername'
617 ];
618
619 public static function isCalendarSlugAvailable($slug, $checkDb = true, $exceptId = false)
620 {
621 if (in_array($slug, self::$reserved)) {
622 return false;
623 }
624
625 if (strlen($slug) < 4) {
626 return false;
627 }
628
629 if ($checkDb) {
630
631 if ($exceptId) {
632 $exist = Calendar::where('slug', $slug)->where('id', '!=', $exceptId)->first();
633 } else {
634 $exist = Calendar::where('slug', $slug)->first();
635 }
636
637 if ($exist) {
638 return false;
639 }
640 }
641
642
643 if (is_numeric($slug)) {
644 return false;
645 }
646
647 // check if $slug has any special characters or any space. We will only allow alpha-numeric values
648 return preg_match('/^[a-zA-Z0-9_-]+$/', $slug);
649 }
650
651 public static function isEventSlugAvailable($slug, $checkDb = true, $calendarId = null, $exceptId = null)
652 {
653 if (in_array($slug, self::$reserved)) {
654 return false;
655 }
656
657 if (strlen($slug) < 4) {
658 return false;
659 }
660
661 if ($checkDb) {
662 $eventQuery = CalendarSlot::where('slug', $slug);
663
664 if ($exceptId) {
665 $eventQuery->where('id', '!=', $exceptId);
666 }
667
668 if ($calendarId) {
669 $eventQuery->where('calendar_id', $calendarId);
670 }
671
672 $exist = $eventQuery->first();
673
674 if ($exist) {
675 return false;
676 }
677 }
678
679 if (is_numeric($slug)) {
680 return false;
681 }
682
683 // check if $slug has any special characters or any space. We will only allow alpha-numeric values
684 return preg_match('/^[a-zA-Z0-9_-]+$/', $slug);
685 }
686
687 public static function getAppBaseUrl($extension = '')
688 {
689 return apply_filters('fluent_booking/admin_base_url', admin_url('admin.php?page=fluent-booking#/' . $extension), $extension);
690 }
691
692 public static function getAdminBookingUrl($bookingId)
693 {
694 return self::getAppBaseUrl('scheduled-events?booking_id=' . $bookingId);
695 }
696
697 public static function getUpgradeUrl()
698 {
699 return 'https://fluentbooking.com/pricing/?utm_source=plugin&utm_medium=wp_install&utm_campaign=fcal_upgrade&theme=' . self::getActiveThemeName();
700 }
701
702 public static function getNextBookingGroup()
703 {
704 $lastBooking = Booking::orderBy('group_id', 'desc')->first(['group_id']);
705
706 if ($lastBooking) {
707 return $lastBooking->group_id + 1;
708 }
709
710 return 1;
711 }
712
713 public static function getNextIndex()
714 {
715 static $index = 0;
716
717 $index += 1;
718
719 return $index;
720 }
721
722 public static function getGlobalPaymentSettings()
723 {
724 static $settings;
725
726 if ($settings) {
727 return $settings;
728 }
729
730 $settings = get_option('fluent_booking_global_payment_settings', []);
731
732 return $settings;
733 }
734
735 public static function isPaymentEnabled($calendarEvent = null)
736 {
737 $settings = CurrenciesHelper::getGlobalCurrencySettings();
738 if (Arr::get($settings, 'is_active') == 'yes') {
739 return true;
740 }
741
742 if ($calendarEvent) {
743 if ($calendarEvent->type != 'paid') {
744 return false;
745 }
746
747 $exist = Meta::where('object_type', 'calendar_slot')
748 ->where('object_id', $calendarEvent->id)
749 ->where('key', 'payment_settings')
750 ->first();
751
752 return $exist && $exist->value && Arr::get($exist->value, 'enabled') == 'yes';
753 }
754
755 return false;
756 }
757
758 public static function isPaymentConfigured($method = 'stripe')
759 {
760 $settings = get_option('fluent_booking_payment_settings_' . $method, []);
761
762 return Arr::get($settings, 'is_active', 'no') == 'yes';
763 }
764
765 /**
766 * Sanitize form inputs recursively.
767 *
768 * @param $input
769 *
770 * @return mixed $input
771 */
772 public static function fluentbookingSanitizer($input, $attribute = null, $fields = [])
773 {
774 if (is_string($input)) {
775 $element = Arr::get($fields, $attribute . '.element');
776
777 if (in_array($element, ['post_content', 'rich_text_input'])) {
778 return wp_kses_post($input);
779 } elseif ('textarea' === $element) {
780 $input = sanitize_textarea_field($input);
781 } elseif ('input_email' === $element) {
782 $input = strtolower(sanitize_text_field($input));
783 } elseif ('input_url' === $element) {
784 $input = sanitize_url($input);
785 } else {
786 $input = sanitize_text_field($input);
787 }
788 } elseif (is_array($input)) {
789 foreach ($input as $key => &$value) {
790 $attribute = $attribute ? $attribute . '[' . $key . ']' : $key;
791
792 $value = self::fluentbookingSanitizer($value, $attribute, $fields);
793
794 $attribute = null;
795 }
796 }
797
798 return $input;
799 }
800
801 public static function getMeta($group, $objectId, $key, $withModel = false)
802 {
803 $meta = Meta::where('object_type', $group)
804 ->where('object_id', $objectId)
805 ->where('key', $key)
806 ->first();
807
808 if ($meta) {
809 if ($withModel) {
810 return $meta;
811 }
812
813 return $meta->value;
814 }
815
816 return null;
817 }
818
819 public static function updateMeta($group, $objectId, $key, $value)
820 {
821 $meta = self::getMeta($group, $objectId, $key, true);
822
823 if ($meta) {
824 $meta->value = $value;
825 $meta->save();
826 return $meta;
827 }
828
829 return Meta::create([
830 'object_type' => $group,
831 'key' => $key,
832 'object_id' => $objectId,
833 'value' => $value
834 ]);
835 }
836
837 public static function deleteMeta($group, $objectId, $key)
838 {
839 return Meta::where('object_type', $group)
840 ->where('object_id', $objectId)
841 ->where('key', $key)
842 ->delete();
843 }
844
845 public static function getBookingMeta($eventId, $metaKey, $withModel = false)
846 {
847 $bookingMeta = BookingMeta::where('booking_id', $eventId)
848 ->where('meta_key', $metaKey)
849 ->first();
850
851 if ($bookingMeta) {
852 return $withModel ? $bookingMeta : $bookingMeta->value;
853 }
854
855 return null;
856 }
857
858 public static function updateBookingMeta($eventId, $metaKey, $value)
859 {
860 $bookingMeta = self::getBookingMeta($eventId, $metaKey, true);
861
862 if ($bookingMeta) {
863 $bookingMeta->value = $value;
864 $bookingMeta->save();
865 return $bookingMeta;
866 }
867
868 return BookingMeta::create([
869 'booking_id' => $eventId,
870 'meta_key' => $metaKey, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
871 'value' => $value
872 ]);
873 }
874
875 public static function getUserDisplayName($userId = null)
876 {
877 if (!$userId) {
878 $userId = get_current_user_id();
879 }
880
881 if (!$userId) {
882 return '';
883 }
884
885 $user = get_user_by('ID', $userId);
886
887 return self::getDisplayNameFromUser($user);
888 }
889
890 public static function getDisplayNameFromUser($user)
891 {
892 if (!$user) {
893 return '';
894 }
895
896 $firstName = is_object($user) ? ($user->first_name ?? '') : '';
897 $lastName = is_object($user) ? ($user->last_name ?? '') : '';
898
899 $name = trim($firstName . ' ' . $lastName);
900
901 if ($name !== '') {
902 return $name;
903 }
904
905 $displayName = is_object($user) ? ($user->display_name ?? '') : '';
906
907 return (string) $displayName;
908 }
909
910 public static function getUserEmail($userId = null)
911 {
912 $userId = $userId ?: get_current_user_id();
913
914 if (!$userId) {
915 return '';
916 }
917
918 $user = get_user_by('ID', $userId);
919
920 return $user->user_email;
921 }
922
923 public static function excerpt($text, $max_length = 160)
924 {
925 // Strip HTML tags and convert entities to their corresponding characters
926 $text = html_entity_decode(wp_strip_all_tags($text));
927
928 // Remove any line breaks, tabs, or extra whitespace
929 $text = preg_replace('/\s+/', ' ', trim($text));
930
931 if (mb_strlen($text) > $max_length) {
932 $text = mb_substr($text, 0, $max_length);
933 $text = preg_replace('/\s+\S+$/', '', $text) . '...';
934 }
935
936 return $text;
937 }
938
939 public static function generateSlotSlug($default, $calendar)
940 {
941 $original = sanitize_title($default, $default, 'display');
942
943 $default = $original;
944
945 $counter = 1;
946
947 while (CalendarSlot::where('calendar_id', $calendar->id)->where('slug', $default)->first()) {
948 $default = $original . '-' . $counter;
949 $counter += 1;
950 }
951
952 return apply_filters('fluent_booking/slot_slug', $default, $original);
953 }
954
955
956 public static function getIp($defalt = '127.0.0.1')
957 {
958 static $ipAddress;
959
960 if ($ipAddress) {
961 return $ipAddress;
962 }
963
964 if (empty($_SERVER['REMOTE_ADDR'])) {
965 // It's a local cli request
966 return $defalt;
967 }
968
969 $ipAddress = '';
970
971 $serverData = $_SERVER;
972 $HTTP_CF_CONNECTING_IP = Arr::get($serverData, 'HTTP_CF_CONNECTING_IP');
973 $RemoteAddr = Arr::get($serverData, 'REMOTE_ADDR');
974 $clientIp = Arr::get($serverData, 'HTTP_CLIENT_IP');
975 $HTTP_X_FORWARDED_FOR = Arr::get($serverData, 'HTTP_X_FORWARDED_FOR');
976 if ($HTTP_CF_CONNECTING_IP) {
977 //If it's a valid Cloudflare request
978
979 if (self::isCfIp($RemoteAddr)) {
980 //Use the CF-Connecting-IP header.
981 $ipAddress = $HTTP_CF_CONNECTING_IP;
982 } else {
983 //If it isn't valid, then use REMOTE_ADDR.
984 $ipAddress = $RemoteAddr;
985 }
986 } else if ($RemoteAddr == '127.0.0.1') {
987 // most probably it's local reverse proxy
988 if ($clientIp) {
989 $ipAddress = $clientIp;
990 } else if ($HTTP_X_FORWARDED_FOR) {
991 $ipAddress = (string)rest_is_ip_address(trim(current(preg_split('/,/', sanitize_text_field($HTTP_X_FORWARDED_FOR)))));
992 }
993 }
994
995 if (!$ipAddress) {
996 $ipAddress = $RemoteAddr;
997 }
998
999 $ipAddress = preg_replace('/^(\d+\.\d+\.\d+\.\d+):\d+$/', '\1', $ipAddress);
1000
1001 $ipAddress = apply_filters('fluent_booking/user_ip', $ipAddress, []);
1002
1003 $ipAddress = sanitize_text_field(wp_unslash($ipAddress));
1004
1005 return $ipAddress;
1006 }
1007
1008 private static function isCfIp($ip = '')
1009 {
1010 if (!$ip) {
1011 $serverData = $_SERVER;
1012 $REMOTE_ADDR = Arr::get($serverData, 'REMOTE_ADDR');
1013 $ip = $REMOTE_ADDR;
1014 }
1015 $cloudflareIPRanges = array(
1016 '173.245.48.0/20',
1017 '103.21.244.0/22',
1018 '103.22.200.0/22',
1019 '103.31.4.0/22',
1020 '141.101.64.0/18',
1021 '108.162.192.0/18',
1022 '190.93.240.0/20',
1023 '188.114.96.0/20',
1024 '197.234.240.0/22',
1025 '198.41.128.0/17',
1026 '162.158.0.0/15',
1027 '104.16.0.0/13',
1028 '104.24.0.0/14',
1029 '172.64.0.0/13',
1030 '131.0.72.0/22',
1031 );
1032 //Make sure that the request came via Cloudflare.
1033 foreach ($cloudflareIPRanges as $range) {
1034 //Use the ip_in_range function from Joomla.
1035 if (self::ipInRange($ip, $range)) {
1036 //IP is valid. Belongs to Cloudflare.
1037 return true;
1038 }
1039 }
1040
1041 return false;
1042 }
1043
1044 private static function ipInRange($ip, $range)
1045 {
1046 if (strpos($range, '/') !== false) {
1047 // $range is in IP/NETMASK format
1048 list($range, $netmask) = explode('/', $range, 2);
1049 if (strpos($netmask, '.') !== false) {
1050 // $netmask is a 255.255.0.0 format
1051 $netmask = str_replace('*', '0', $netmask);
1052 $netmask_dec = ip2long($netmask);
1053 return ((ip2long($ip) & $netmask_dec) == (ip2long($range) & $netmask_dec));
1054 } else {
1055 // $netmask is a CIDR size block
1056 // fix the range argument
1057 $x = explode('.', $range);
1058 while (count($x) < 4) $x[] = '0';
1059 list($a, $b, $c, $d) = $x;
1060 $range = sprintf("%u.%u.%u.%u", empty($a) ? '0' : $a, empty($b) ? '0' : $b, empty($c) ? '0' : $c, empty($d) ? '0' : $d);
1061 $range_dec = ip2long($range);
1062 $ip_dec = ip2long($ip);
1063
1064 # Strategy 1 - Create the netmask with 'netmask' 1s and then fill it to 32 with 0s
1065 #$netmask_dec = bindec(str_pad('', $netmask, '1') . str_pad('', 32-$netmask, '0'));
1066
1067 # Strategy 2 - Use math to create it
1068 $wildcard_dec = pow(2, (32 - $netmask)) - 1;
1069 $netmask_dec = ~$wildcard_dec;
1070
1071 return (($ip_dec & $netmask_dec) == ($range_dec & $netmask_dec));
1072 }
1073 } else {
1074 // range might be 255.255.*.* or 1.2.3.0-1.2.3.255
1075 if (strpos($range, '*') !== false) { // a.b.*.* format
1076 // Just convert to A-B format by setting * to 0 for A and 255 for B
1077 $lower = str_replace('*', '0', $range);
1078 $upper = str_replace('*', '255', $range);
1079 $range = "$lower-$upper";
1080 }
1081
1082 if (strpos($range, '-') !== false) { // A-B format
1083 list($lower, $upper) = explode('-', $range, 2);
1084 $lower_dec = (float)sprintf("%u", ip2long($lower));
1085 $upper_dec = (float)sprintf("%u", ip2long($upper));
1086 $ip_dec = (float)sprintf("%u", ip2long($ip));
1087 return (($ip_dec >= $lower_dec) && ($ip_dec <= $upper_dec));
1088 }
1089 return false;
1090 }
1091 }
1092
1093 public static function fcal_sanitize_html($html)
1094 {
1095 if (!$html) {
1096 return $html;
1097 }
1098
1099 // Return $html if it's just a plain text
1100 if (!preg_match('/<[^>]*>/', $html)) {
1101 return $html;
1102 }
1103
1104 $tags = wp_kses_allowed_html('post');
1105 $tags['style'] = [
1106 'types' => [],
1107 ];
1108 // iframe
1109 $tags['iframe'] = [
1110 'width' => [],
1111 'height' => [],
1112 'src' => [],
1113 'title' => [],
1114 'frameborder' => [],
1115 'allow' => [],
1116 'class' => [],
1117 'id' => [],
1118 'allowfullscreen' => [],
1119 'style' => [],
1120 ];
1121 //button
1122 $tags['button'] = [
1123 'onclick' => []
1124 ];
1125
1126 //svg
1127 if (empty($tags['svg'])) {
1128 $svg_args = [
1129 'svg' => [
1130 'class' => true,
1131 'aria-hidden' => true,
1132 'aria-labelledby' => true,
1133 'role' => true,
1134 'xmlns' => true,
1135 'width' => true,
1136 'height' => true,
1137 'viewbox' => true,
1138 ],
1139 'g' => ['fill' => true],
1140 'title' => ['title' => true],
1141 'path' => [
1142 'd' => true,
1143 'fill' => true,
1144 'transform' => true,
1145 ],
1146 ];
1147 $tags = array_merge($tags, $svg_args);
1148 }
1149
1150 $tags = apply_filters('fluent_booking/allowed_html_tags', $tags);
1151
1152 return wp_kses($html, $tags);
1153 }
1154
1155 /**
1156 * Sanitize inputs recursively.
1157 *
1158 * @param array $input
1159 * @param array $sanitizeMap
1160 *
1161 * @return array $input
1162 */
1163 public static function fcal_backend_sanitizer($inputs, $sanitizeMap = [])
1164 {
1165 $originalValues = $inputs;
1166 foreach ($inputs as $key => &$value) {
1167 if (is_array($value)) {
1168 $value = self::fcal_backend_sanitizer($value, $sanitizeMap);
1169 } else {
1170 $method = Arr::get($sanitizeMap, $key);
1171 if (!$method) {
1172 continue;
1173 }
1174 if (is_callable($method)) {
1175 $value = call_user_func($method, $value);
1176 } elseif (method_exists(self::class, $method)) {
1177 $value = call_user_func([self::class, $method], $value);
1178 }
1179 }
1180 }
1181
1182 return apply_filters('fluent_booking/backend_sanitized_values', $inputs, $originalValues);
1183 }
1184
1185 /**
1186 * Recursively implode a multi-dimentional array
1187 *
1188 * @param string $glue
1189 * @param array $array
1190 *
1191 * @return string
1192 */
1193 public static function fcalImplodeRecursive($glue, array $array)
1194 {
1195 $fn = function ($glue, array $array) use (&$fn) {
1196 $result = '';
1197 foreach ($array as $item) {
1198 if (is_array($item)) {
1199 $result .= $fn($glue, $item);
1200 } else {
1201 $result .= $glue . $item;
1202 }
1203 }
1204
1205 return $result;
1206 };
1207
1208 return ltrim($fn($glue, $array), $glue);
1209 }
1210
1211 public static function getEventColors()
1212 {
1213 return apply_filters('fluent_booking/event_colors', [
1214 [
1215 'label' => __('Red-Orange', 'fluent-booking'),
1216 'value' => '#ff4f00'
1217 ],
1218 [
1219 'label' => __('Deep Lilac', 'fluent-booking'),
1220 'value' => '#e55cff'
1221 ],
1222 [
1223 'label' => __('Purple', 'fluent-booking'),
1224 'value' => '#8247f5'
1225 ],
1226 [
1227 'label' => __('Vivid Blue', 'fluent-booking'),
1228 'value' => '#0099ff'
1229 ],
1230 [
1231 'label' => __('Cyan', 'fluent-booking'),
1232 'value' => '#0ae8f0'
1233 ],
1234 [
1235 'label' => __('Emerald Green', 'fluent-booking'),
1236 'value' => '#17e885'
1237 ],
1238 [
1239 'label' => __('Lime Green', 'fluent-booking'),
1240 'value' => '#ccf000'
1241 ],
1242 [
1243 'label' => __('Amber', 'fluent-booking'),
1244 'value' => '#ffa600'
1245 ]
1246 ]);
1247 }
1248
1249 public static function getMeetingDurations()
1250 {
1251 return apply_filters('fluent_booking/meeting_durations_schema', [
1252 [
1253 'value' => '15',
1254 'label' => __('15 Minutes', 'fluent-booking')
1255 ],
1256 [
1257 'value' => '30',
1258 'label' => __('30 Minutes', 'fluent-booking')
1259 ],
1260 [
1261 'value' => '45',
1262 'label' => __('45 Minutes', 'fluent-booking')
1263 ],
1264 [
1265 'value' => '60',
1266 'label' => __('60 Minutes', 'fluent-booking')
1267 ],
1268 [
1269 'value' => 'custom',
1270 'label' => __('Custom', 'fluent-booking')
1271 ]
1272 ]);
1273 }
1274
1275 public static function getMeetingMultiDurations()
1276 {
1277 return apply_filters('fluent_booking/meeting_multi_durations_schema', [
1278 [
1279 'value' => '5',
1280 'label' => __('5 Minutes', 'fluent-booking')
1281 ],
1282 [
1283 'value' => '10',
1284 'label' => __('10 Minutes', 'fluent-booking')
1285 ],
1286 [
1287 'value' => '15',
1288 'label' => __('15 Minutes', 'fluent-booking')
1289 ],
1290 [
1291 'value' => '30',
1292 'label' => __('30 Minutes', 'fluent-booking')
1293 ],
1294 [
1295 'value' => '45',
1296 'label' => __('45 Minutes', 'fluent-booking')
1297 ],
1298 [
1299 'value' => '50',
1300 'label' => __('50 Minutes', 'fluent-booking')
1301 ],
1302 [
1303 'value' => '60',
1304 'label' => __('60 Minutes', 'fluent-booking')
1305 ],
1306 [
1307 'value' => '90',
1308 'label' => __('90 Minutes', 'fluent-booking')
1309 ],
1310 [
1311 'value' => '120',
1312 'label' => __('120 Minutes', 'fluent-booking')
1313 ],
1314 [
1315 'value' => '150',
1316 'label' => __('150 Minutes', 'fluent-booking')
1317 ],
1318 [
1319 'value' => '180',
1320 'label' => __('180 Minutes', 'fluent-booking')
1321 ],
1322 [
1323 'value' => '240',
1324 'label' => __('240 Minutes', 'fluent-booking')
1325 ],
1326 [
1327 'value' => '480',
1328 'label' => __('480 Minutes', 'fluent-booking')
1329 ]
1330 ]);
1331 }
1332
1333 public static function getDurationLookup($multiDuration = false)
1334 {
1335 $durations = $multiDuration ? self::getMeetingMultiDurations() : self::getMeetingDurations();
1336
1337 $durationLookup = [];
1338 foreach ($durations as $duration) {
1339 $durationLookup[$duration['value']] = $duration['label'];
1340 }
1341
1342 return $durationLookup;
1343 }
1344
1345 public static function formatDuration($totalMinutes)
1346 {
1347 $days = floor($totalMinutes / 1440); // 1440 minutes in a day
1348 $hours = floor(($totalMinutes % 1440) / 60);
1349 $minutes = $totalMinutes % 60;
1350
1351 $formattedDuration = [];
1352 if ($days > 0) {
1353 $unit = $days > 1 ? __('Days', 'fluent-booking') : __('Day', 'fluent-booking');
1354 $formattedDuration[] = $days . ' ' . $unit;
1355 }
1356
1357 if ($hours > 0) {
1358 $unit = $hours > 1 ? __('Hours', 'fluent-booking') : __('Hour', 'fluent-booking');
1359 $formattedDuration[] = $hours . ' ' . $unit;
1360 }
1361
1362 if ($minutes > 0 || empty($formattedDuration)) {
1363 $unit = $minutes > 1 ? __('Minutes', 'fluent-booking') : __('Minute', 'fluent-booking');
1364 $formattedDuration[] = $minutes . ' ' . $unit;
1365 }
1366
1367 return implode(' ', $formattedDuration);
1368 }
1369
1370 public static function getBufferTimes()
1371 {
1372 return apply_filters('fluent_booking/buffer_times_schema', [
1373 [
1374 'value' => '0',
1375 'label' => __('No buffer time', 'fluent-booking')
1376 ],
1377 [
1378 'value' => '5',
1379 'label' => __('5 Minutes', 'fluent-booking')
1380 ],
1381 [
1382 'value' => '10',
1383 'label' => __('10 Minutes', 'fluent-booking')
1384 ],
1385 [
1386 'value' => '15',
1387 'label' => __('15 Minutes', 'fluent-booking')
1388 ],
1389 [
1390 'value' => '20',
1391 'label' => __('20 Minutes', 'fluent-booking')
1392 ],
1393 [
1394 'value' => '30',
1395 'label' => __('30 Minutes', 'fluent-booking')
1396 ],
1397 [
1398 'value' => '45',
1399 'label' => __('45 Minutes', 'fluent-booking')
1400 ],
1401 [
1402 'value' => '60',
1403 'label' => __('60 Minutes', 'fluent-booking')
1404 ],
1405 [
1406 'value' => '90',
1407 'label' => __('90 Minutes', 'fluent-booking')
1408 ],
1409 [
1410 'value' => '120',
1411 'label' => __('120 Minutes', 'fluent-booking')
1412 ]
1413 ]);
1414 }
1415
1416 public static function getSlotIntervals()
1417 {
1418 return apply_filters('fluent_booking/slot_intervals_schema', [
1419 [
1420 'value' => '',
1421 'label' => __('Use event length (default)', 'fluent-booking')
1422 ],
1423 [
1424 'value' => '5',
1425 'label' => __('5 Minutes', 'fluent-booking')
1426 ],
1427 [
1428 'value' => '10',
1429 'label' => __('10 Minutes', 'fluent-booking')
1430 ],
1431 [
1432 'value' => '15',
1433 'label' => __('15 Minutes', 'fluent-booking')
1434 ],
1435 [
1436 'value' => '20',
1437 'label' => __('20 Minutes', 'fluent-booking')
1438 ],
1439 [
1440 'value' => '30',
1441 'label' => __('30 Minutes', 'fluent-booking')
1442 ],
1443 [
1444 'value' => '45',
1445 'label' => __('45 Minutes', 'fluent-booking')
1446 ],
1447 [
1448 'value' => '60',
1449 'label' => __('60 Minutes', 'fluent-booking')
1450 ],
1451 [
1452 'value' => '75',
1453 'label' => __('75 Minutes', 'fluent-booking')
1454 ],
1455 [
1456 'value' => '90',
1457 'label' => __('90 Minutes', 'fluent-booking')
1458 ],
1459 [
1460 'value' => '105',
1461 'label' => __('105 Minutes', 'fluent-booking')
1462 ],
1463 [
1464 'value' => '120',
1465 'label' => __('120 Minutes', 'fluent-booking')
1466 ]
1467 ]);
1468 }
1469
1470 public static function getBookingStatusChangingTimes()
1471 {
1472 return apply_filters('fluent_booking/booking_status_changing_times_schema', [
1473 [
1474 'value' => '5',
1475 'label' => __('5 Minutes', 'fluent-booking')
1476 ],
1477 [
1478 'value' => '10',
1479 'label' => __('10 Minutes', 'fluent-booking')
1480 ],
1481 [
1482 'value' => '20',
1483 'label' => __('20 Minutes', 'fluent-booking')
1484 ],
1485 [
1486 'value' => '30',
1487 'label' => __('30 Minutes', 'fluent-booking')
1488 ],
1489 [
1490 'value' => '40',
1491 'label' => __('40 Minutes', 'fluent-booking')
1492 ],
1493 [
1494 'value' => '50',
1495 'label' => __('50 Minutes', 'fluent-booking')
1496 ],
1497 [
1498 'value' => '60',
1499 'label' => __('1 Hour', 'fluent-booking')
1500 ],
1501 [
1502 'value' => '120',
1503 'label' => __('2 Hours', 'fluent-booking')
1504 ],
1505 [
1506 'value' => '180',
1507 'label' => __('3 Hours', 'fluent-booking')
1508 ],
1509 [
1510 'value' => '360',
1511 'label' => __('6 Hours', 'fluent-booking')
1512 ],
1513 [
1514 'value' => '720',
1515 'label' => __('12 Hours', 'fluent-booking')
1516 ],
1517 [
1518 'value' => '1440',
1519 'label' => __('1 Day', 'fluent-booking')
1520 ],
1521 [
1522 'value' => '2880',
1523 'label' => __('2 Days', 'fluent-booking')
1524 ]
1525 ]);
1526 }
1527
1528 public static function getBookingPeriodOptions()
1529 {
1530 return apply_filters('fluent_booking/booking_period_options', [
1531 'upcoming' => __('Upcoming', 'fluent-booking'),
1532 'completed' => __('Completed', 'fluent-booking'),
1533 'pending' => __('Pending', 'fluent-booking'),
1534 'cancelled' => __('Cancelled', 'fluent-booking'),
1535 'all' => __('All', 'fluent-booking'),
1536 ]);
1537 }
1538
1539 public static function getWeekSelectTimes()
1540 {
1541 return apply_filters('fluent_booking/week_select_times_schema', [
1542 'start' => '00:00',
1543 'step' => '00:15',
1544 'end' => '23:45'
1545 ]);
1546 }
1547
1548 public static function getOverrideSelectTimes()
1549 {
1550 return apply_filters('fluent_booking/override_select_times_schema', [
1551 'start' => '00:00',
1552 'step' => '00:15',
1553 'end' => '23:45'
1554 ]);
1555 }
1556
1557 public static function getWeeklyScheduleSchema()
1558 {
1559 return apply_filters('fluent_booking/weekly_schedule_schema', [
1560 'sun' => [
1561 'enabled' => false,
1562 'slots' => []
1563 ],
1564 'mon' => [
1565 'enabled' => true,
1566 'slots' => [
1567 ['start' => '09:00', 'end' => '17:00']
1568 ],
1569 ],
1570 'tue' => [
1571 'enabled' => true,
1572 'slots' => [
1573 ['start' => '09:00', 'end' => '17:00']
1574 ],
1575 ],
1576 'wed' => [
1577 'enabled' => true,
1578 'slots' => [
1579 ['start' => '09:00', 'end' => '17:00']
1580 ],
1581 ],
1582 'thu' => [
1583 'enabled' => true,
1584 'slots' => [
1585 ['start' => '09:00', 'end' => '17:00']
1586 ],
1587 ],
1588 'fri' => [
1589 'enabled' => true,
1590 'slots' => [
1591 ['start' => '09:00', 'end' => '17:00']
1592 ],
1593 ],
1594 'sat' => [
1595 'enabled' => false,
1596 'slots' => []
1597 ],
1598 ]);
1599 }
1600
1601 public static function getCustomFieldTypes()
1602 {
1603 return apply_filters('fluent_booking/custom_fields_types', [
1604 [
1605 'value' => 'email',
1606 'label' => __('Email', 'fluent-booking')
1607 ],
1608 [
1609 'value' => 'text',
1610 'label' => __('Text', 'fluent-booking')
1611 ],
1612 [
1613 'value' => 'textarea',
1614 'label' => __('Textarea', 'fluent-booking')
1615 ],
1616 [
1617 'value' => 'number',
1618 'label' => __('Number', 'fluent-booking')
1619 ],
1620 [
1621 'value' => 'phone',
1622 'label' => __('Phone', 'fluent-booking')
1623 ],
1624 [
1625 'value' => 'radio',
1626 'label' => __('Radio', 'fluent-booking')
1627 ],
1628 [
1629 'value' => 'dropdown',
1630 'label' => __('Select', 'fluent-booking')
1631 ],
1632 [
1633 'value' => 'multi-select',
1634 'label' => __('Multi Select', 'fluent-booking')
1635 ],
1636 [
1637 'value' => 'checkbox',
1638 'label' => __('Checkbox', 'fluent-booking')
1639 ],
1640 [
1641 'value' => 'checkbox-group',
1642 'label' => __('Checkbox Group', 'fluent-booking')
1643 ],
1644 [
1645 'value' => 'date',
1646 'label' => __('Date', 'fluent-booking')
1647 ],
1648 [
1649 'value' => 'file',
1650 'label' => __('File', 'fluent-booking')
1651 ],
1652 [
1653 'value' => 'hidden',
1654 'label' => __('Hidden', 'fluent-booking')
1655 ],
1656 [
1657 'value' => 'terms-and-conditions',
1658 'label' => __('Terms & Conditions', 'fluent-booking')
1659 ]
1660 ]);
1661 }
1662
1663 public static function getDefaultTermsAndConditions()
1664 {
1665 $termsAndConditions = __('I have read and agree to the <a href="#" target="_blank" rel="noopener">Terms and Conditions</a> and <a href="#" target="_blank" rel="noopener">Privacy Policy</a>.', 'fluent-booking');
1666
1667 return apply_filters('fluent_booking/default_terms_and_conditions', $termsAndConditions);
1668 }
1669
1670 public static function getDefaultEmailNotificationSettings()
1671 {
1672 $assetUrl = App::getInstance()['url.assets'];
1673
1674 $checkImage = $assetUrl . 'images/check-mark.png';
1675 $cancelImage = $assetUrl . 'images/cancel-mark.png';
1676 $scheduleImage = $assetUrl . 'images/schedule-mark.png';
1677
1678 return apply_filters('fluent_booking/default_email_notification_settings', [
1679 'booking_conf_attendee' => [
1680 'enabled' => true,
1681 'title' => __('Booking Confirmation Email to Attendee', 'fluent-booking'),
1682 'email' => [
1683 'subject' => 'Booking Confirmation between {{host.name}} & {{guest.full_name}}',
1684 'body' => '<p style="text-align: center;"><img class="alignnone wp-image-76" src="' . $checkImage . '" alt="" width="60" height="60" /></p><h2 class="p1" style="text-align: center;">Your event has been scheduled</h2><hr /><p><strong>Event Name</strong></p><p>{{booking.event_name}} with {{host.name}}</p><p><strong>When</strong></p><p>{{booking.full_start_end_guest_timezone}}</p><p><strong>Who</strong></p><ul><li>{{host.name}} - Organizer</li><li>{{guest.full_name}} - you</li></ul><p><strong>Where</strong></p><p>{{booking.location_details_html}}</p><p><strong>Additional notes</strong></p><p>{{guest.note}}</p><hr /><p style="text-align: center;">' . __('Need to make a change?', 'fluent-booking') . ' <a href="##booking.reschedule_url##">' . __('Reschedule', 'fluent-booking') . '</a> or <a href="##booking.cancelation_url##">' . __('Cancel', 'fluent-booking') . '</p><hr/>' . self::getAddToCalendarHtml($assetUrl)
1685 ],
1686 ],
1687 'booking_conf_host' => [
1688 'enabled' => true,
1689 'is_host' => true,
1690 'title' => __('Booking Confirmation Email to Organizer (You)', 'fluent-booking'),
1691 'email' => [
1692 'additional_recipients' => '',
1693 'subject' => 'New Booking: {{guest.full_name}} @ {{booking.start_date_time_for_host}}',
1694 'body' => '<p style="text-align: center;"><img class="alignnone wp-image-76" src="' . $checkImage . '" alt="" width="60" height="60" /></p><h2 class="p1" style="text-align: center;">A new event has been scheduled</h2><hr /><p><strong>Event Name</strong></p><p>{{booking.event_name}} with {{guest.full_name}}</p><p><strong>When</strong></p><p>{{booking.full_start_end_host_timezone}}</p><p><strong>Who</strong></p><ul><li>{{host.name}} - Organizer</li><li>{{guest.full_name}} ({{guest.email}}) - Guest</li></ul><p><strong>Where</strong></p><p>{{booking.location_details_html}}</p><p><strong>Note</strong></p><p>{{guest.note}}</p><p><strong>Additional Data</strong></p><p>{{guest.form_data_html}}</p><hr /><p style="text-align: center;"><a href="##booking.admin_booking_url##">View on the Website</a></p>'
1695 ],
1696 ],
1697 'reminder_to_attendee' => [
1698 'enabled' => false,
1699 'title' => __('Configure Meeting Reminder to Attendee', 'fluent-booking'),
1700 'email' => [
1701 'subject' => 'Meeting Reminder with {{host.name}} @ {{booking.start_date_time_for_attendee}}',
1702 'body' => '<h2 style="text-align: center;">Reminder: Your meeting will start in {{booking.start_time_human_format}}</h2><hr /><p><strong>Event Name</strong></p><p>{{booking.event_name}} with {{host.name}}</p><h3><strong>When</strong></h3><p>{{booking.full_start_end_guest_timezone}}</p><h3><strong>Who</strong></h3><ul><li>{{host.name}} - Organizer</li><li>{{guest.full_name}} - you</li></ul><p><strong>Where</strong></p><p>{{booking.location_details_html}}</p><p><strong>Additional notes</strong></p><p>{{guest.note}}</p><hr /><p style="text-align: center;">' . __('Need to make a change?', 'fluent-booking') . ' <a href="##booking.reschedule_url##">' . __('Reschedule', 'fluent-booking') . '</a> or <a href="##booking.cancelation_url##">' . __('Cancel', 'fluent-booking') . '</a></p>',
1703 'times' => [
1704 [
1705 'unit' => 'minutes',
1706 'value' => 15,
1707 ]
1708 ]
1709 ],
1710 ],
1711 'reminder_to_host' => [
1712 'enabled' => false,
1713 'is_host' => true,
1714 'title' => __('Configure Meeting Reminder to Organizer (You)', 'fluent-booking'),
1715 'email' => [
1716 'additional_recipients' => '',
1717 'subject' => 'Meeting Reminder with {{host.name}} @ {{booking.start_date_time_for_host}}',
1718 'body' => '<h2 style="text-align: center;">Reminder: Your meeting will start in {{booking.start_time_human_format}}</h2><hr /><p><strong>Event Name</strong></p><p>{{booking.event_name}} with {{guest.full_name}}</p><p><strong>When</strong></p><p>{{booking.full_start_end_host_timezone}}</p><p><strong>Who</strong></p><ul><li>{{host.name}} - Organizer</li><li>{{guest.full_name}} ({{guest.email}}) - Guest</li></ul><p><strong>Where</strong></p><p>{{booking.location_details_html}}</p><p><strong>Note</strong></p><p>{{guest.note}}</p><p><strong>Additional Data</strong></p><p>{{guest.form_data_html}}</p><hr /><p style="text-align: center;"><a href="##booking.admin_booking_url##">View on the Website</a></p>',
1719 'times' => [
1720 [
1721 'unit' => 'minutes',
1722 'value' => 15,
1723 ]
1724 ]
1725 ],
1726 ],
1727 'cancelled_by_attendee' => [
1728 'enabled' => true,
1729 'is_host' => true,
1730 'title' => __('Booking Cancelled by Attendee (email to Organizer)', 'fluent-booking'),
1731 'email' => [
1732 'additional_recipients' => '',
1733 'subject' => 'A booking was cancelled with {{guest.full_name}}',
1734 'body' => '<p style="text-align: center;"><img class="alignnone wp-image-76" src="' . $cancelImage . '" alt="" width="60" height="60" /></p><h2 style="text-align: center;">Booking Cancellation</h2><hr /><p>A scheduled meeting has been canceled. Here are the details:</p><p><strong>Event Name</strong></p><p>{{booking.event_name}} with {{guest.full_name}}</p><p><strong>When</strong></p><p>{{booking.full_start_end_host_timezone}} <span style="color: #ff0000;"><strong>(cancelled)</strong></span></p><p><strong>Cancellation Reason</strong></p><p>{{booking.cancel_reason}}</p><p><strong>Who</strong></p><ul><li>{{host.name}} - Organizer</li><li>{{guest.full_name}} ({{guest.email}}) - Guest</li></ul><p><strong>Where</strong></p><p>{{booking.location_details_html}}</p><p><strong>Note</strong></p><p>{{guest.note}}</p><p><strong>Additional Data</strong></p><p>{{guest.form_data_html}}</p><hr /><p style="text-align: center;"><a href="##booking.admin_booking_url##">View on the Website</a></p>'
1735 ],
1736 ],
1737 'cancelled_by_host' => [
1738 'enabled' => true,
1739 'title' => __('Booking Cancelled by Organizer (email to Attendee)', 'fluent-booking'),
1740 'email' => [
1741 'subject' => 'Your booking was cancelled with {{host.name}}',
1742 'body' => '<p style="text-align: center;"><img class="alignnone wp-image-76" src="' . $cancelImage . '" alt="" width="60" height="60" /></p><h2 style="text-align: center;">Booking Cancellation</h2><hr /><p>Your scheduled meeting has been canceled. Here are the details:</p><p><strong>Event Name</strong></p><p>{{booking.event_name}} with {{guest.full_name}}</p><p><strong>When</strong></p><p>{{booking.full_start_end_host_timezone}} <span style="color: #ff0000;"><strong>(cancelled)</strong></span></p><p><strong>Cancellation Reason</strong></p><p>{{booking.cancel_reason}}</p>'
1743 ],
1744 ],
1745 'rescheduled_by_attendee' => [
1746 'enabled' => true,
1747 'is_host' => true,
1748 'title' => __('Booking Rescheduled by Attendee (email to Organizer)', 'fluent-booking'),
1749 'email' => [
1750 'additional_recipients' => '',
1751 'subject' => 'A booking was rescheduled with {{guest.full_name}}',
1752 'body' => '<p style="text-align: center;"><img class="alignnone wp-image-76" src="' . $scheduleImage . '" alt="" width="60" height="60" /></p><h2 style="text-align: center;">Booking Rescheduled</h2><hr /><p>A scheduled meeting has been rescheduled. Here are the details:</p><p><strong>Event Name</strong></p><p>{{booking.event_name}} with {{guest.full_name}}</p><p><strong>When</strong></p><p>New Time: {{booking.full_start_end_host_timezone}} <span style="color: #ff0000;"><strong>(new)</strong></span></p><p>Previous Time: {{booking.previous_meeting_time}}</p><p><strong>Rescheduling Reason</strong></p><p>{{booking.reschedule_reason}}</p><p><strong>Who</strong></p><ul><li>{{host.name}} - Organizer</li><li>{{guest.full_name}} ({{guest.email}}) - Guest</li></ul><p><strong>Where</strong></p><p>{{booking.location_details_html}}</p><p><strong>Note</strong></p><p>{{guest.note}}</p><p><strong>Additional Data</strong></p><p>{{guest.form_data_html}}</p><hr /><p style="text-align: center;"><a href="##booking.admin_booking_url##">View on the Website</a></p>'
1753 ],
1754 ],
1755 'rescheduled_by_host' => [
1756 'enabled' => true,
1757 'title' => __('Booking Rescheduled by Organizer (email to Attendee)', 'fluent-booking'),
1758 'email' => [
1759 'subject' => 'Your booking was rescheduled with {{host.name}}',
1760 'body' => '<p style="text-align: center;"><img class="alignnone wp-image-76" src="' . $scheduleImage . '" alt="" width="60" height="60" /></p><h2 style="text-align: center;">Booking Rescheduled</h2><hr /><p>Your scheduled meeting has been rescheduled. Here are the details:</p><p><strong>Event Name</strong></p><p>{{booking.event_name}} with {{guest.full_name}}</p><p><strong>When</strong></p><p>New Time: {{booking.full_start_end_host_timezone}} <span style="color: #ff0000;"><strong>(new)</strong></span></p><p>Previous Time: {{booking.previous_meeting_time_guest_timezone}}</p><p><strong>Rescheduling Reason</strong></p><p>{{booking.reschedule_reason}}</p><hr /><p style="text-align: center;">' . __('Need to make a change?', 'fluent-booking') . ' <a href="##booking.reschedule_url##">' . __('Reschedule', 'fluent-booking') . '</a> or <a href="##booking.cancelation_url##">' . __('Cancel', 'fluent-booking') . '</a></p><hr/>' . self::getAddToCalendarHtml($assetUrl)
1761 ],
1762 ],
1763 'booking_request_host' => [
1764 'enabled' => true,
1765 'is_host' => true,
1766 'title' => __('Booking Approval Request to Host (email to Organizer)', 'fluent-booking'),
1767 'email' => [
1768 'additional_recipients' => '',
1769 'subject' => 'Awaiting Approval: {{guest.full_name}} @ {{booking.start_date_time_for_host}}',
1770 'body' => '<p style="text-align: center;"><img class="alignnone wp-image-76" src="' . $scheduleImage . '" alt="" width="60" height="60" /></p><h2 class="p1" style="text-align: center;">A booking is still waiting for your approval</h2><hr /><p>Someone has requested to schedule an event on your calendar. Here are the details:</p><p><strong>Event Name</strong></p><p>{{booking.event_name}} with {{guest.full_name}}</p><p><strong>When</strong></p><p>{{booking.full_start_end_host_timezone}}</p><p><strong>Who</strong></p><ul><li>{{host.name}} - Organizer</li><li>{{guest.full_name}} ({{guest.email}}) - Guest</li></ul><p><strong>Where</strong></p><p>{{booking.location_details_html}}</p><p><strong>Note</strong></p><p>{{guest.note}}</p><p><strong>Additional Data</strong></p><p>{{guest.form_data_html}}</p><hr />' . self::getConfirmAndRejectButton($assetUrl) . '<p style="text-align: center;"><a href="##booking.admin_booking_url##">View on the Website</a></p>'
1771 ],
1772 ],
1773 'booking_request_attendee' => [
1774 'enabled' => true,
1775 'title' => __('Booking Submission Confirmation (email to Attendee)', 'fluent-booking'),
1776 'email' => [
1777 'subject' => 'Booking Submitted: Meeting between {{host.name}} & {{guest.full_name}}',
1778 'body' => '<p style="text-align: center;"><img class="alignnone wp-image-76" src="' . $scheduleImage . '" alt="" width="60" height="60" /></p><h2 class="p1" style="text-align: center;">Your booking has been submitted</h2><hr /><p>Please wait for the host to confirm your booking.</p><p><strong>Event Name</strong></p><p>{{booking.event_name}} with {{host.name}}</p><p><strong>When</strong></p><p>{{booking.full_start_end_guest_timezone}}</p><p><strong>Who</strong></p><ul><li>{{host.name}} - Organizer</li><li>{{guest.full_name}} - you</li></ul><p><strong>Where</strong></p><p>{{booking.location_details_html}}</p><p><strong>Additional notes</strong></p><p>{{guest.note}}</p><hr /><p style="text-align: center;">' . __('Need to make a change?', 'fluent-booking') . ' <a href="##booking.reschedule_url##">' . __('Reschedule', 'fluent-booking') . '</a> or <a href="##booking.cancelation_url##">' . __('Cancel', 'fluent-booking') . '</p>'
1779 ],
1780 ],
1781 'declined_by_host' => [
1782 'enabled' => true,
1783 'title' => __('Booking Declined by Organizer (email to Attendee)', 'fluent-booking'),
1784 'email' => [
1785 'subject' => 'Booking Declined: Your booking was declined with {{host.name}}',
1786 'body' => '<p style="text-align: center;"><img class="alignnone wp-image-76" src="' . $cancelImage . '" alt="" width="60" height="60" /></p><h2 style="text-align: center;">Booking Declined</h2><hr /><p>Your booking request has been declined. Here are the details:</p><p><strong>Event Name</strong></p><p>{{booking.event_name}} with {{guest.full_name}}</p><p><strong>When</strong></p><p>{{booking.full_start_end_host_timezone}} <span style="color: #ff0000;"><strong>(declined)</strong></span></p><p><strong>Reason</strong></p><p>{{booking.reject_reason}}</p>'
1787 ],
1788 ],
1789 ]);
1790 }
1791
1792 public static function getAddToCalendarHtml($assetUrl = '')
1793 {
1794 $assetUrl = $assetUrl ?: App::getInstance()['url.assets'];
1795
1796 $html = '<table style="margin: 0 auto; border: none;"><tbody><tr><td style="border: none; font-size: 1rem;">' . __('Add to calendar', 'fluent-booking') . '</td><td style="border: 1px solid black; padding: 5px 5px 0 5px;"><a href="##add_to_g_calendar_url##"><img width="20" height="20" src="' . $assetUrl . 'images/g-icon.png" alt="Google Calendar" /></a></td><td style="border: 1px solid black; padding: 5px 5px 0 5px;"><a href="##add_to_ol_calendar_url##"><img width="20" height="20" src="' . $assetUrl . 'images/ol-icon.png" alt="Outlook" /></a></td><td style="border: 1px solid black; padding: 5px 5px 0 5px;"><a href="##add_to_ms_calendar_url##"><img width="20" height="20" src="' . $assetUrl . 'images/msoffice.png" alt="Microsoft Office" /></a></td><td style="border: 1px solid black; padding: 5px 5px 0 5px;"><a href="##add_to_ics_calendar_url##"><img width="20" height="20" src="' . $assetUrl . 'images/ics.png" alt="Other Calendar" /></a></td></tr></tbody></table>';
1797
1798 return apply_filters('fluent_booking/add_to_calendar_html', $html);
1799 }
1800
1801 public static function getConfirmAndRejectButton($assetUrl = '')
1802 {
1803 $assetUrl = $assetUrl ?: App::getInstance()['url.assets'];
1804
1805 $html = '<table style="margin: 16px auto; border: none;"><tbody><tr><td style="border: none; border-radius: 3px;" align="center" valign="middle"><p style="display: inline-block; background: #292929; color: #ffffff; font-size: 14px; font-weight: 500; line-height: 16px; margin: 0; text-decoration: none; text-transform: none; padding: 10px 16px 10px 14px; border-radius: 6px; box-sizing: border-box; height: 36px;"><a href="##booking.booking_confirm_url##" style="color: #ffffff; text-decoration: none; display: flex; margin: auto;"><img src="' . $assetUrl . 'images/confirm-mark.png" style="height: 16px; width: 16px; margin-left: 0; margin-right: 0.5rem;" alt="" width="16px" data-bit="iit" />' . __('Confirm', 'fluent-booking') . '</a></p><p style="width: 16px; height: 16px; display: inline-block;"> </p><p style="display: inline-block; background: #ffffff; border: 1px solid #d1d5db; color: #ffffff; font-size: 14px; font-weight: 500; line-height: 16px; margin: 0; text-decoration: none; text-transform: none; padding: 10px 16px 10px 14px; border-radius: 6px; box-sizing: border-box; height: 36px;"><a href="##booking.booking_reject_url##" style="color: #292929; text-decoration: none; display: flex; margin: auto;"><img src="' . $assetUrl . 'images/reject-mark.png" style="height: 16px; width: 16px; margin-left: 0; margin-right: 8px;" alt="" width="16px" data-bit="iit" />' . __('Reject', 'fluent-booking') . '</a></p></td></tr></tbody></table>';
1806
1807 return apply_filters('fluent_booking/confirm_and_reject_button_html', $html);
1808 }
1809
1810 public static function getIframeHtml()
1811 {
1812 $html = '<iframe id="fluentbooking" loading="lazy" height="700px" width="100%" style="min-width:320px;height:700px;" frameborder="0" src="##landing_page_url##"></iframe>';
1813
1814 return apply_filters('fluent_booking/get_iframe_html', $html);
1815 }
1816
1817 public static function getEditorShortCodes($calendarEvent = null, $isHtmlSupported = false, $iframeHtml = '')
1818 {
1819 if (!$isHtmlSupported) {
1820 $groups = [
1821 'guest' => [
1822 'title' => __('Attendee Data', 'fluent-booking'),
1823 'key' => 'guest',
1824 'shortcodes' => [
1825 '{{guest.first_name}}' => __('Guest First Name', 'fluent-booking'),
1826 '{{guest.last_name}}' => __('Guest Last Name', 'fluent-booking'),
1827 '{{guest.full_name}}' => __('Guest Full Name', 'fluent-booking'),
1828 '{{guest.email}}' => __('Guest Email', 'fluent-booking'),
1829 '{{guest.note}}' => __('Guest Note', 'fluent-booking'),
1830 '{{booking.phone}}' => __('Guest Main Phone Number (if provided)', 'fluent-booking'),
1831 '{{guest.timezone}}' => __('Guest Timezone', 'fluent-booking'),
1832 '{{guest.total_guest}}' => __('Total Guest Count', 'fluent-booking')
1833 ]
1834 ],
1835 'booking' => [
1836 'title' => __('Booking Data', 'fluent-booking'),
1837 'key' => 'booking',
1838 'shortcodes' => [
1839 '{{booking.event_name}}' => __('Event Name', 'fluent-booking'),
1840 '{{booking.description}}' => __('Event Description', 'fluent-booking'),
1841 '{{booking.booking_title}}' => __('Booking Title', 'fluent-booking'),
1842 '{{booking.additional_guests}}' => __('Additional Guests', 'fluent-booking'),
1843 '{{booking.full_start_end_guest_timezone}}' => __('Full Start Date Time (with guest timezone)', 'fluent-booking'),
1844 '{{booking.full_start_end_host_timezone}}' => __('Full Start Date Time (with host timezone)', 'fluent-booking'),
1845 '{{booking.full_start_and_end_guest_timezone}}' => __('Full Start & End Date Time (with guest timezone)', 'fluent-booking'),
1846 '{{booking.full_start_and_end_host_timezone}}' => __('Full Start & End Date Time (with host timezone)', 'fluent-booking'),
1847 '{{booking.all_bookings_short_times_guest_timezone}}' => __('All Bookings Short Times (with guest timezone)', 'fluent-booking'),
1848 '{{booking.all_bookings_short_times_host_timezone}}' => __('All Bookings Short Times (with host timezone)', 'fluent-booking'),
1849 '{{booking.all_bookings_full_times_guest_timezone}}' => __('All Bookings Full Times (with guest timezone)', 'fluent-booking'),
1850 '{{booking.all_bookings_full_times_host_timezone}}' => __('All Bookings Full Times (with host timezone)', 'fluent-booking'),
1851 '{{booking.start_date_time}}' => __('Event Date Time (UTC)', 'fluent-booking'),
1852 '{{booking.start_date_time_for_attendee}}' => __('Event Date Time (with attendee timezone)', 'fluent-booking'),
1853 '{{booking.start_date_time_for_host}}' => __('Event Date Time (with host timezone)', 'fluent-booking'),
1854 '{{booking.start_date_time_for_attendee.format.Y-m-d}}' => __('Event Date Time (with attendee timezone) (Ex: 2024-05-20)', 'fluent-booking'),
1855 '{{booking.start_date_time_for_host.format.Y-m-d}}' => __('Event Date Time (with host timezone) (Ex: 2024-05-20)', 'fluent-booking'),
1856 '{{booking.location_details_text}}' => __('Event Location Details', 'fluent-booking'),
1857 '{{booking.cancel_reason}}' => __('Event Cancel Reason', 'fluent-booking'),
1858 '{{booking.start_time_human_format}}' => __('Event Start Time (ex: 2 hours from now)', 'fluent-booking'),
1859 '##booking.cancelation_url##' => __('Booking Cancellation URL', 'fluent-booking'),
1860 '##booking.reschedule_url##' => __('Booking Reschedule URL', 'fluent-booking'),
1861 '##booking.admin_booking_url##' => __('Booking Details Admin URL', 'fluent-booking'),
1862 '{{booking.source_url}}' => __('Source URL', 'fluent-booking'),
1863 '{{booking.utm_source}}' => __('UTM Source', 'fluent-booking'),
1864 '{{booking.utm_medium}}' => __('UTM Medium', 'fluent-booking'),
1865 '{{booking.utm_campaign}}' => __('UTM Campaign', 'fluent-booking'),
1866 '{{booking.utm_term}}' => __('UTM Term', 'fluent-booking'),
1867 '{{booking.utm_content}}' => __('UTM Content', 'fluent-booking'),
1868 '{{booking.booking_hash}}' => __('Unique Booking Hash', 'fluent-booking'),
1869 '{{booking.reschedule_reason}}' => __('Event Reschedule Reason', 'fluent-booking'),
1870 '{{booking.previous_meeting_date_time_host_timezone}}' => __('Previous Meeting Date & Time (with host timezone)', 'fluent-booking'),
1871 '{{booking.previous_meeting_date_time_guest_timezone}}' => __('Previous Meeting Date & Time (with guest timezone)', 'fluent-booking'),
1872 ]
1873 ],
1874 'host' => [
1875 'title' => __('Host Data', 'fluent-booking'),
1876 'key' => 'host',
1877 'shortcodes' => [
1878 '{{host.name}}' => __('Host Name', 'fluent-booking'),
1879 '{{host.email}}' => __('Host Email', 'fluent-booking'),
1880 '{{host.timezone}}' => __('Host Timezone', 'fluent-booking'),
1881 ]
1882 ],
1883 'other' => [
1884 'title' => __('Other', 'fluent-booking'),
1885 'key' => 'other',
1886 'shortcodes' => [
1887 '{{event.id}}' => __('Event ID', 'fluent-booking'),
1888 '{{calendar.id}}' => __('Calendar ID', 'fluent-booking'),
1889 '{{event.title}}' => __('Event Title', 'fluent-booking'),
1890 '{{calendar.title}}' => __('Calendar Title', 'fluent-booking'),
1891 '{{calendar.description}}' => __('Calendar Description', 'fluent-booking'),
1892 '{{add_booking_to_calendar}}' => __('Add Booking to Calendar', 'fluent-booking'),
1893 ]
1894 ]
1895 ];
1896 } else {
1897 $groups = [
1898 'guest' => [
1899 'title' => __('Attendee Data', 'fluent-booking'),
1900 'key' => 'guest',
1901 'shortcodes' => [
1902 '{{guest.first_name}}' => __('Guest First Name', 'fluent-booking'),
1903 '{{guest.last_name}}' => __('Guest Last Name', 'fluent-booking'),
1904 '{{guest.full_name}}' => __('Guest Full Name', 'fluent-booking'),
1905 '{{guest.email}}' => __('Guest Email', 'fluent-booking'),
1906 '{{booking.phone}}' => __('Guest Main Phone Number (if provided)', 'fluent-booking'),
1907 '{{guest.note}}' => __('Guest Note', 'fluent-booking'),
1908 '{{guest.timezone}}' => __('Guest Timezone', 'fluent-booking'),
1909 '{{guest.total_guest}}' => __('Total Guest Count', 'fluent-booking')
1910 ]
1911 ],
1912 'booking' => [
1913 'title' => __('Booking Data', 'fluent-booking'),
1914 'key' => 'booking',
1915 'shortcodes' => [
1916 '{{booking.event_name}}' => __('Event Name', 'fluent-booking'),
1917 '{{booking.description}}' => __('Event Description', 'fluent-booking'),
1918 '{{booking.booking_title}}' => __('Booking Title', 'fluent-booking'),
1919 '{{booking.additional_guests}}' => __('Additional Guests', 'fluent-booking'),
1920 '{{booking.full_start_end_guest_timezone}}' => __('Full Start Date Time (with guest timezone)', 'fluent-booking'),
1921 '{{booking.full_start_end_host_timezone}}' => __('Full Start Date Time (with host timezone)', 'fluent-booking'),
1922 '{{booking.full_start_and_end_guest_timezone}}' => __('Full Start & End Date Time (with guest timezone)', 'fluent-booking'),
1923 '{{booking.full_start_and_end_host_timezone}}' => __('Full Start & End Date Time (with host timezone)', 'fluent-booking'),
1924 '{{booking.all_bookings_short_times_guest_timezone}}' => __('All Bookings Short Times (with guest timezone)', 'fluent-booking'),
1925 '{{booking.all_bookings_short_times_host_timezone}}' => __('All Bookings Short Times (with host timezone)', 'fluent-booking'),
1926 '{{booking.all_bookings_full_times_guest_timezone}}' => __('All Bookings Full Times (with guest timezone)', 'fluent-booking'),
1927 '{{booking.all_bookings_full_times_host_timezone}}' => __('All Bookings Full Times (with host timezone)', 'fluent-booking'),
1928 '{{booking.start_date_time}}' => __('Event Date Time (UTC)', 'fluent-booking'),
1929 '{{booking.start_date_time_for_attendee}}' => __('Event Date Time (with guest timezone)', 'fluent-booking'),
1930 '{{booking.start_date_time_for_host}}' => __('Event Date Time (with host timezone)', 'fluent-booking'),
1931 '{{booking.start_date_time_for_attendee.format.Y-m-d}}' => __('Event Date Time (with attendee timezone) (Ex: 2024-05-20)', 'fluent-booking'),
1932 '{{booking.start_date_time_for_host.format.Y-m-d}}' => __('Event Date Time (with host timezone) (Ex: 2024-05-20)', 'fluent-booking'),
1933 '{{booking.location_details_html}}' => __('Event Location Details (HTML)', 'fluent-booking'),
1934 '{{booking.cancel_reason}}' => __('Event Cancel Reason', 'fluent-booking'),
1935 '{{booking.start_time_human_format}}' => __('Event Start Time (ex: 2 hours from now)', 'fluent-booking'),
1936 '##booking.cancelation_url##' => __('Booking Cancellation URL', 'fluent-booking'),
1937 '##booking.reschedule_url##' => __('Booking Reschedule URL', 'fluent-booking'),
1938 '##booking.admin_booking_url##' => __('Booking Details Admin URL', 'fluent-booking'),
1939 '{{booking.source_url}}' => __('Source URL', 'fluent-booking'),
1940 '{{booking.utm_source}}' => __('UTM Source', 'fluent-booking'),
1941 '{{booking.utm_medium}}' => __('UTM Medium', 'fluent-booking'),
1942 '{{booking.utm_campaign}}' => __('UTM Campaign', 'fluent-booking'),
1943 '{{booking.utm_term}}' => __('UTM Term', 'fluent-booking'),
1944 '{{booking.utm_content}}' => __('UTM Content', 'fluent-booking'),
1945 '{{booking.booking_hash}}' => __('Unique Booking Hash', 'fluent-booking'),
1946 '{{booking.reschedule_reason}}' => __('Event Reschedule Reason', 'fluent-booking'),
1947 '{{booking.previous_meeting_date_time_host_timezone}}' => __('Previous Meeting Date & Time (with host timezone)', 'fluent-booking'),
1948 '{{booking.previous_meeting_date_time_guest_timezone}}' => __('Previous Meeting Date & Time (with guest timezone)', 'fluent-booking'),
1949 ]
1950 ],
1951 'host' => [
1952 'title' => __('Host Data', 'fluent-booking'),
1953 'key' => 'host',
1954 'shortcodes' => [
1955 '{{host.name}}' => __('Host Name', 'fluent-booking'),
1956 '{{host.email}}' => __('Host Email', 'fluent-booking'),
1957 '{{host.timezone}}' => __('Host Timezone', 'fluent-booking'),
1958 ]
1959 ],
1960 'other' => [
1961 'title' => __('Other', 'fluent-booking'),
1962 'key' => 'other',
1963 'shortcodes' => [
1964 '{{event.id}}' => __('Event ID', 'fluent-booking'),
1965 '{{event.calendar_id}}' => __('Calendar ID', 'fluent-booking'),
1966 '{{event.title}}' => __('Event Title', 'fluent-booking'),
1967 '{{calendar.title}}' => __('Calendar Title', 'fluent-booking'),
1968 '{{calendar.description}}' => __('Calendar Description', 'fluent-booking'),
1969 '{{add_booking_to_calendar}}' => __('Add Booking to Calendar', 'fluent-booking'),
1970 ]
1971 ]
1972 ];
1973 }
1974
1975 if ($calendarEvent) {
1976 $customFields = BookingFieldService::getCustomFields($calendarEvent, true);
1977 foreach ($customFields as $fieldKey => $field) {
1978 $groups['booking']['shortcodes']['{{booking.custom.' . $fieldKey . '}}'] = $field['label'];
1979 if ($field['type'] == 'date') {
1980 $groups['booking']['shortcodes']['{{booking.custom.' . $fieldKey . '.format.Y-m-d}}'] = $field['label'] . ' (Ex: 2024-05-20)';
1981 }
1982 }
1983
1984 if (Helper::isPaymentEnabled($calendarEvent)) {
1985 $groups['payment'] = [
1986 'title' => __('Payment Data', 'fluent-booking'),
1987 'key' => 'payment',
1988 'shortcodes' => [
1989 '{{payment.payment_total}}' => __('Payment Total', 'fluent-booking'),
1990 '{{payment.payment_status}}' => __('Payment Status', 'fluent-booking'),
1991 '{{payment.payment_method}}' => __('Payment Method', 'fluent-booking'),
1992 '{{payment.currency}}' => __('Currency', 'fluent-booking'),
1993 '{{payment.payment_date}}' => __('Payment Date', 'fluent-booking'),
1994 ]
1995 ];
1996
1997 if ($isHtmlSupported) {
1998 $groups['payment']['shortcodes']['{{payment.receipt_html}}'] = __('Payment Receipt (HTML)', 'fluent-booking');
1999 }
2000 }
2001
2002 if ($calendarEvent->isMultiHostsEvent()) {
2003 $totalhost = count($calendarEvent->getHostIds());
2004 for ($i = 1; $i < $totalhost; $i++) {
2005 $groups['host']['shortcodes']['{{team_member.' . $i . '.name}}'] = __('Team Member ', 'fluent-booking') . $i . __(' Name', 'fluent-booking');
2006 $groups['host']['shortcodes']['{{team_member.' . $i . '.email}}'] = __('Team Member ', 'fluent-booking') . $i . __(' Email', 'fluent-booking');
2007 }
2008 }
2009 }
2010
2011 return apply_filters('fluent_booking/editor_shortcodes_groups', $groups, $calendarEvent, $isHtmlSupported);
2012 }
2013
2014 public static function encryptKey($value)
2015 {
2016 if (!$value) {
2017 return $value;
2018 }
2019
2020 if (!extension_loaded('openssl')) {
2021 return $value;
2022 }
2023
2024 $salt = (defined('LOGGED_IN_SALT') && '' !== LOGGED_IN_SALT) ? LOGGED_IN_SALT : 'this-is-a-fallback-salt-but-not-secure';
2025
2026 if (defined('FLUENT_BOOKING_ENCRYPTION_KEY')) {
2027 $key = FLUENT_BOOKING_ENCRYPTION_KEY;
2028 } else {
2029 $key = (defined('LOGGED_IN_KEY') && '' !== LOGGED_IN_KEY) ? LOGGED_IN_KEY : 'this-is-a-fallback-key-but-not-secure';
2030 }
2031
2032 $method = 'aes-256-ctr';
2033 $ivlen = openssl_cipher_iv_length($method);
2034 $iv = openssl_random_pseudo_bytes($ivlen);
2035
2036 $raw_value = openssl_encrypt($value . $salt, $method, $key, 0, $iv);
2037 if (!$raw_value) {
2038 return false;
2039 }
2040
2041 return base64_encode($iv . $raw_value); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
2042 }
2043
2044 public static function decryptKey($raw_value)
2045 {
2046
2047 if (!$raw_value) {
2048 return $raw_value;
2049 }
2050
2051 if (!extension_loaded('openssl')) {
2052 return $raw_value;
2053 }
2054
2055 $raw_value = base64_decode($raw_value, true); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
2056 if ($raw_value === false) {
2057 return false;
2058 }
2059
2060 $method = 'aes-256-ctr';
2061 $ivlen = openssl_cipher_iv_length($method);
2062 if ($ivlen === false || strlen($raw_value) <= $ivlen) {
2063 return false;
2064 }
2065 $iv = substr($raw_value, 0, $ivlen);
2066
2067 $raw_value = substr($raw_value, $ivlen);
2068
2069 if (defined('FLUENT_BOOKING_ENCRYPTION_KEY')) {
2070 $key = FLUENT_BOOKING_ENCRYPTION_KEY;
2071 } else {
2072 $key = (defined('LOGGED_IN_KEY') && '' !== LOGGED_IN_KEY) ? LOGGED_IN_KEY : 'this-is-a-fallback-key-but-not-secure';
2073 }
2074
2075 $salt = (defined('LOGGED_IN_SALT') && '' !== LOGGED_IN_SALT) ? LOGGED_IN_SALT : 'this-is-a-fallback-salt-but-not-secure';
2076
2077 $value = openssl_decrypt($raw_value, $method, $key, 0, $iv);
2078 if (!is_string($value) || substr($value, -strlen($salt)) !== $salt) {
2079 return false;
2080 }
2081
2082 return substr($value, 0, -strlen($salt));
2083 }
2084
2085 public static function debugLog($data)
2086 {
2087 if (defined('FLUENT_BOOKING_DEBUG') && FLUENT_BOOKING_DEBUG) {
2088 error_log(print_r($data, true)); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log,WordPress.PHP.DevelopmentFunctions.error_log_print_r
2089 }
2090 }
2091
2092 public static function getGlobalSettings($settingsKey = null)
2093 {
2094 $defaults = [
2095 'payments' => [
2096 'currency' => 'USD',
2097 'is_active' => 'no'
2098 ],
2099 'emailing' => [
2100 'from_name' => '',
2101 'from_email' => '',
2102 'reply_to_name' => '',
2103 'reply_to_email' => '',
2104 'use_host_name' => '',
2105 'use_host_email_on_reply' => '',
2106 'attach_ics_on_confirmation' => '',
2107 'email_footer' => ''
2108 ],
2109 'administration' => [
2110 'admin_email' => '{{wp.admin_email}}',
2111 'summary_notification' => 'no',
2112 'notification_frequency' => 'daily',
2113 'notification_day' => 'mon',
2114 'start_day' => 'sun',
2115 'auto_cancel_timing' => '10',
2116 'auto_complete_timing' => '60',
2117 'default_country' => ''
2118 ],
2119 'time_format' => '12',
2120 'theme' => 'system-default'
2121 ];
2122
2123 $settings = get_option('_fluent_booking_settings', []);
2124
2125 if (empty($settings)) {
2126 $settings = [];
2127 }
2128
2129 $settings['payments'] = CurrenciesHelper::getGlobalCurrencySettings();
2130
2131 $settings = wp_parse_args($settings, $defaults);
2132
2133 $emailSettings = $settings['emailing'];
2134
2135 if (empty($settings['administration']['auto_cancel_timing'])) {
2136 $settings['administration']['auto_cancel_timing'] = '10';
2137 }
2138
2139 if (empty($settings['administration']['auto_complete_timing'])) {
2140 $settings['administration']['auto_complete_timing'] = '10';
2141 }
2142
2143 if (empty($emailSettings['from_name']) && defined('FLUENTCRM')) {
2144 $crmSettings = fluentcrmGetGlobalSettings('email_settings', []);
2145 $emailSettings['from_name'] = Arr::get($crmSettings, 'from_name');
2146 if (empty($emailSettings['from_email'])) {
2147 $emailSettings['from_email'] = Arr::get($crmSettings, 'from_email');
2148 }
2149 if (empty($emailSettings['reply_to_name'])) {
2150 $emailSettings['reply_to_name'] = Arr::get($crmSettings, 'reply_to_name');
2151 }
2152 if (empty($emailSettings['reply_to_email'])) {
2153 $emailSettings['reply_to_email'] = Arr::get($crmSettings, 'reply_to_email');
2154 }
2155
2156 $settings['emailing'] = $emailSettings;
2157 }
2158
2159 if ($settingsKey) {
2160 return Arr::get($settings, $settingsKey, []);
2161 }
2162
2163 return $settings;
2164
2165 }
2166
2167 public static function getGlobalAdminSetting($key = null, $default = null)
2168 {
2169 static $settings;
2170 if ($settings) {
2171
2172 if ($key) {
2173 return Arr::get($settings, $key, $default);
2174 }
2175
2176 return $settings;
2177 }
2178
2179 $globalSettings = self::getGlobalSettings();
2180 $settings = Arr::get($globalSettings, 'administration', []);
2181
2182 if ($key) {
2183 return Arr::get($settings, $key, $default);
2184 }
2185
2186 return $settings;
2187 }
2188
2189 public static function getDefaultTimeFormat()
2190 {
2191 static $format;
2192
2193 if ($format) {
2194 return $format;
2195 }
2196
2197 $settings = self::getGlobalSettings();
2198 $format = Arr::get($settings, 'time_format', '24');
2199 return $format;
2200 }
2201
2202 public static function getDefaultBookingFilters()
2203 {
2204 return apply_filters('fluent_booking/default_booking_filters', [
2205 'period' => 'upcoming',
2206 'author' => 'me', // me, all, calendar_id
2207 'event' => 'all',
2208 'event_type' => 'all'
2209 ]);
2210 }
2211
2212 public static function getDefaultPaginations()
2213 {
2214 return apply_filters('fluent_booking/default_paginations', [
2215 'bookings' => 10,
2216 'calendars' => 10,
2217 'coupons' => 10,
2218 'availabilities' => 10
2219 ]);
2220 }
2221
2222 public static function getVerifiedSenders()
2223 {
2224 $verifiedSenders = [];
2225 if (defined('FLUENTMAIL')) {
2226 $smtpSettings = get_option('fluentmail-settings', []);
2227 if ($smtpSettings && count($smtpSettings['mappings'])) {
2228 $verifiedSenders = array_keys($smtpSettings['mappings']);
2229 }
2230 }
2231 /**
2232 * Filter the verified email senders
2233 * @param array $verifiedSenders
2234 */
2235 return apply_filters('fluent_booking/verfied_email_senders', $verifiedSenders);
2236 }
2237
2238 public static function getBookingReceiptLandingBaseUrl()
2239 {
2240 return apply_filters('fluent_booking/booking_receipt_landing_base_url', site_url('/'));
2241 }
2242
2243 public static function getGlobalModuleSettings($cached = true)
2244 {
2245 static $settings = null;
2246
2247 if ($cached && $settings !== null) {
2248 return $settings;
2249 }
2250
2251 $settings = get_option('_fluent_booking_enabled_modules', []);
2252
2253 if (empty($settings) || !\is_array($settings)) {
2254 $settings = [];
2255 }
2256
2257 return $settings;
2258 }
2259
2260 public static function updateGlobalModuleSettings($settings = [])
2261 {
2262 if (!is_array($settings)) {
2263 $settings = [];
2264 }
2265
2266 update_option('_fluent_booking_enabled_modules', $settings);
2267
2268 return self::getGlobalModuleSettings(false);
2269 }
2270
2271 public static function isModuleEnabled($module)
2272 {
2273 $settings = self::getGlobalModuleSettings();
2274
2275 return isset($settings[$module]) && $settings[$module] === 'yes';
2276 }
2277
2278 /**
2279 * @return mixed|void
2280 */
2281 public static function fluentbooking_is_rtl()
2282 {
2283 /**
2284 * If FluentBooking is running on RTL Mode
2285 *
2286 * @param bool $is_rtl
2287 */
2288 return apply_filters('fluent_booking/is_rtl', is_rtl());
2289 }
2290
2291 public static function fluentBookingUserAvatar($id_or_email, $args)
2292 {
2293 if (empty($id_or_email)) {
2294 return '';
2295 }
2296 return apply_filters('fluent_booking/author_photo', get_avatar_url($id_or_email), $args);
2297 }
2298
2299 public static function getPrefSettings($cached = true)
2300 {
2301 static $pref = null;
2302
2303 if ($cached && $pref) {
2304 return $pref;
2305 }
2306
2307 $settings = [
2308 'frontend' => [
2309 'enabled' => 'no',
2310 'slug' => 'my-bookings',
2311 'render_type' => 'standalone',
2312 'page_id' => ''
2313 ],
2314 'coupon' => [
2315 'enabled' => 'no'
2316 ]
2317 ];
2318
2319 $storedSettings = get_option('fluent_booking_modules', []);
2320
2321 if ($storedSettings && is_array($storedSettings)) {
2322 $settings = wp_parse_args($storedSettings, $settings);
2323 } else {
2324 update_option('fluent_booking_modules', $settings, 'yes');
2325 }
2326
2327 $pref = $settings;
2328
2329 return $settings;
2330 }
2331
2332 public static function getPrefSettins($cached = true)
2333 {
2334 return self::getPrefSettings($cached);
2335 }
2336
2337 public static function getFeatures()
2338 {
2339 return apply_filters('fluent_booking/get_features', [
2340 'has_fluentcrm' => defined('FLUENTCRM'),
2341 'has_fluentsmtp' => defined('FLUENTMAIL'),
2342 'has_fluentform' => defined('FLUENTFORM'),
2343 'has_fluentboards' => defined('FLUENT_BOARDS'),
2344 'has_fluentcart' => defined('FLUENTCART_VERSION')
2345 ]);
2346 }
2347
2348 public static function getActiveThemeName()
2349 {
2350 $ins = get_option('_fb_ins_by');
2351
2352 if ($ins) {
2353 return sanitize_text_field($ins);
2354 }
2355
2356 return get_option('template');
2357 }
2358
2359 /**
2360 * Per-IP fixed-window rate limiter for public AJAX/REST endpoints.
2361 *
2362 * @param string $action Action name (e.g. apply_coupon, schedule_meeting).
2363 * @param int $limit Max requests per window.
2364 * @param int $window Window in seconds.
2365 * @return bool True if under the limit (and the count was incremented),
2366 * false if over.
2367 */
2368 public static function checkRateLimit($action, $limit, $window = 60)
2369 {
2370 $args = apply_filters('fluent_booking/public_ajax_ratelimit', [
2371 'limit' => $limit,
2372 'window' => $window,
2373 ], $action);
2374
2375 $limit = max(1, (int) (isset($args['limit']) ? $args['limit'] : $limit));
2376 $window = max(1, (int) (isset($args['window']) ? $args['window'] : $window));
2377
2378 $key = 'fcal_ratelimit_' . $action . '_' . md5(self::getIp());
2379 $count = (int) get_transient($key);
2380
2381 if ($count >= $limit) {
2382 return false;
2383 }
2384
2385 set_transient($key, $count + 1, $window);
2386
2387 return true;
2388 }
2389 }
2390