PluginProbe
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution / trunk
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution vtrunk
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
← All changes | app/Services/Helper.php +442 -84 1.5.23trunk View file →
@@ -693,11 +693,44 @@
693 693 {
694 694 return self::getAppBaseUrl('scheduled-events?booking_id=' . $bookingId);
695 695 }
696 696
697 - public static function getUpgradeUrl()
697 + /**
698 + * Build a spec-compliant "Upgrade to Pro" URL.
699 + *
700 + * Follows the shared Fluent* UTM spec:
701 + * utm_source = fluent-booking (fixed vocabulary, never the wp.org slug)
702 + * utm_medium = free_plugin | pro_plugin (acquisition vs cross-sell)
703 + * utm_campaign= upgrade_pro (override for xsell_<target> / license_* )
704 + * utm_content = the exact placement, e.g. feature_lock_team_calendar, upgrade_page
705 + * utm_term = plugin version that generated the link
706 + * utm_id = promo id, blank normally (omit unless passed)
707 + *
708 + * @param string $content The utm_content placement.
709 + * @param array $overrides Override any utm_* param (e.g. utm_campaign for cross-sell).
710 + * @return string
711 + */
712 + public static function getUpgradeUrl($content = 'upgrade_page', $overrides = [])
698 713 {
699 - return 'https://fluentbooking.com/pricing/?utm_source=plugin&utm_medium=wp_install&utm_campaign=fcal_upgrade';
714 + $baseUrl = apply_filters(
715 + 'fluent_booking/pro_upgrade_base_url',
716 + 'https://fluentbooking.com/pricing/'
717 + );
718 +
719 + $params = wp_parse_args($overrides, [
720 + 'utm_source' => 'fluent-booking',
721 + 'utm_medium' => defined('FLUENT_BOOKING_PRO_VERSION') ? 'pro_plugin' : 'free_plugin',
722 + 'utm_campaign' => 'upgrade_pro',
723 + 'utm_content' => $content,
724 + 'utm_term' => FLUENT_BOOKING_VERSION,
725 + ]);
726 +
727 + // Drop any blank params (e.g. an unset utm_id) so they never hit the URL.
728 + $params = array_filter($params, function ($value) {
729 + return $value !== '' && $value !== null;
730 + });
731 +
732 + return add_query_arg($params, $baseUrl);
700 733 }
701 734
702 735 public static function getNextBookingGroup()
703 736 {
@@ -714,8 +747,9 @@
714 747 {
715 748 static $index = 0;
716 749
717 750 $index += 1;
751 +
718 752 return $index;
719 753 }
720 754
721 755 public static function getGlobalPaymentSettings()
@@ -727,21 +761,14 @@
727 761 }
728 762
729 763 $settings = get_option('fluent_booking_global_payment_settings', []);
730 764
731 - if (!$settings) {
732 - $settings = [
733 - 'currency' => 'USD',
734 - 'is_active' => 'no'
735 - ];
736 - }
737 -
738 765 return $settings;
739 766 }
740 767
741 768 public static function isPaymentEnabled($calendarEvent = null)
742 769 {
743 - $settings = self::getGlobalPaymentSettings();
770 + $settings = CurrenciesHelper::getGlobalCurrencySettings();
744 771 if (Arr::get($settings, 'is_active') == 'yes') {
745 772 return true;
746 773 }
747 774
@@ -889,15 +916,29 @@
889 916 }
890 917
891 918 $user = get_user_by('ID', $userId);
892 919
893 - $name = trim($user->first_name . ' ' . $user->last_name);
920 + return self::getDisplayNameFromUser($user);
921 + }
894 922
895 - if ($name) {
923 + public static function getDisplayNameFromUser($user)
924 + {
925 + if (!$user) {
926 + return '';
927 + }
928 +
929 + $firstName = is_object($user) ? ($user->first_name ?? '') : '';
930 + $lastName = is_object($user) ? ($user->last_name ?? '') : '';
931 +
932 + $name = trim($firstName . ' ' . $lastName);
933 +
934 + if ($name !== '') {
896 935 return $name;
897 936 }
898 937
899 - return $user->display_name;
938 + $displayName = is_object($user) ? ($user->display_name ?? '') : '';
939 +
940 + return (string) $displayName;
900 941 }
901 942
902 943 public static function getUserEmail($userId = null)
903 944 {
@@ -943,24 +984,174 @@
943 984
944 985 return apply_filters('fluent_booking/slot_slug', $default, $original);
945 986 }
946 987
947 - public static function getIp()
988 +
989 + public static function getIp($defalt = '127.0.0.1')
948 990 {
949 - $server = $_SERVER;
991 + static $ipAddress;
950 992
951 - $clientIp = Arr::get($server, 'HTTP_CLIENT_IP');
952 - $xForwarded = Arr::get($server, 'HTTP_X_FORWARDED_FOR');
993 + if ($ipAddress) {
994 + return $ipAddress;
995 + }
953 996
954 - if (!empty($clientIp)) {
955 - $ip = $clientIp;
956 - } elseif (!empty($xForwarded)) {
957 - $ip = $clientIp;
997 + if (empty($_SERVER['REMOTE_ADDR'])) {
998 + // It's a local cli request
999 + return $defalt;
1000 + }
1001 +
1002 + $ipAddress = '';
1003 +
1004 + $serverData = $_SERVER;
1005 + $HTTP_CF_CONNECTING_IP = Arr::get($serverData, 'HTTP_CF_CONNECTING_IP');
1006 + $RemoteAddr = Arr::get($serverData, 'REMOTE_ADDR');
1007 + $clientIp = Arr::get($serverData, 'HTTP_CLIENT_IP');
1008 + $HTTP_X_FORWARDED_FOR = Arr::get($serverData, 'HTTP_X_FORWARDED_FOR');
1009 + if ($HTTP_CF_CONNECTING_IP) {
1010 + //If it's a valid Cloudflare request
1011 +
1012 + if (self::isCfIp($RemoteAddr)) {
1013 + //Use the CF-Connecting-IP header.
1014 + $ipAddress = $HTTP_CF_CONNECTING_IP;
1015 + } else {
1016 + //If it isn't valid, then use REMOTE_ADDR.
1017 + $ipAddress = $RemoteAddr;
1018 + }
1019 + } else if ($RemoteAddr == '127.0.0.1') {
1020 + // most probably it's local reverse proxy
1021 + if ($clientIp) {
1022 + $ipAddress = $clientIp;
1023 + } else if ($HTTP_X_FORWARDED_FOR) {
1024 + $ipAddress = (string)rest_is_ip_address(trim(current(preg_split('/,/', sanitize_text_field($HTTP_X_FORWARDED_FOR)))));
1025 + }
1026 + }
1027 +
1028 + if (!$ipAddress) {
1029 + $ipAddress = $RemoteAddr;
1030 + }
1031 +
1032 + $ipAddress = preg_replace('/^(\d+\.\d+\.\d+\.\d+):\d+$/', '\1', $ipAddress);
1033 +
1034 + $ipAddress = apply_filters('fluent_booking/user_ip', $ipAddress, []);
1035 +
1036 + $ipAddress = sanitize_text_field(wp_unslash($ipAddress));
1037 +
1038 + return $ipAddress;
1039 + }
1040 +
1041 + /**
1042 + * Valid E.164 number: 7-15 significant digits.
1043 + *
1044 + * @param string $phone
1045 + * @return bool
1046 + */
1047 + public static function isValidPhoneNumber($phone)
1048 + {
1049 + if (!apply_filters('fluent_booking/enforce_phone_validation', true)) {
1050 + return true;
1051 + }
1052 +
1053 + $phone = trim((string)$phone);
1054 +
1055 + if ($phone === '') {
1056 + return false;
1057 + }
1058 +
1059 + if (!preg_match('/^\+?[0-9\s().\-]+$/', $phone)) {
1060 + return false;
1061 + }
1062 +
1063 + $digitCount = strlen(preg_replace('/\D/', '', $phone));
1064 +
1065 + $min = (int)apply_filters('fluent_booking/phone_min_digits', 7);
1066 + $max = (int)apply_filters('fluent_booking/phone_max_digits', 15);
1067 +
1068 + return $digitCount >= $min && $digitCount <= $max;
1069 + }
1070 +
1071 + private static function isCfIp($ip = '')
1072 + {
1073 + if (!$ip) {
1074 + $serverData = $_SERVER;
1075 + $REMOTE_ADDR = Arr::get($serverData, 'REMOTE_ADDR');
1076 + $ip = $REMOTE_ADDR;
1077 + }
1078 + $cloudflareIPRanges = array(
1079 + '173.245.48.0/20',
1080 + '103.21.244.0/22',
1081 + '103.22.200.0/22',
1082 + '103.31.4.0/22',
1083 + '141.101.64.0/18',
1084 + '108.162.192.0/18',
1085 + '190.93.240.0/20',
1086 + '188.114.96.0/20',
1087 + '197.234.240.0/22',
1088 + '198.41.128.0/17',
1089 + '162.158.0.0/15',
1090 + '104.16.0.0/13',
1091 + '104.24.0.0/14',
1092 + '172.64.0.0/13',
1093 + '131.0.72.0/22',
1094 + );
1095 + //Make sure that the request came via Cloudflare.
1096 + foreach ($cloudflareIPRanges as $range) {
1097 + //Use the ip_in_range function from Joomla.
1098 + if (self::ipInRange($ip, $range)) {
1099 + //IP is valid. Belongs to Cloudflare.
1100 + return true;
1101 + }
1102 + }
1103 +
1104 + return false;
1105 + }
1106 +
1107 + private static function ipInRange($ip, $range)
1108 + {
1109 + if (strpos($range, '/') !== false) {
1110 + // $range is in IP/NETMASK format
1111 + list($range, $netmask) = explode('/', $range, 2);
1112 + if (strpos($netmask, '.') !== false) {
1113 + // $netmask is a 255.255.0.0 format
1114 + $netmask = str_replace('*', '0', $netmask);
1115 + $netmask_dec = ip2long($netmask);
1116 + return ((ip2long($ip) & $netmask_dec) == (ip2long($range) & $netmask_dec));
1117 + } else {
1118 + // $netmask is a CIDR size block
1119 + // fix the range argument
1120 + $x = explode('.', $range);
1121 + while (count($x) < 4) $x[] = '0';
1122 + list($a, $b, $c, $d) = $x;
1123 + $range = sprintf("%u.%u.%u.%u", empty($a) ? '0' : $a, empty($b) ? '0' : $b, empty($c) ? '0' : $c, empty($d) ? '0' : $d);
1124 + $range_dec = ip2long($range);
1125 + $ip_dec = ip2long($ip);
1126 +
1127 + # Strategy 1 - Create the netmask with 'netmask' 1s and then fill it to 32 with 0s
1128 + #$netmask_dec = bindec(str_pad('', $netmask, '1') . str_pad('', 32-$netmask, '0'));
1129 +
1130 + # Strategy 2 - Use math to create it
1131 + $wildcard_dec = pow(2, (32 - $netmask)) - 1;
1132 + $netmask_dec = ~$wildcard_dec;
1133 +
1134 + return (($ip_dec & $netmask_dec) == ($range_dec & $netmask_dec));
1135 + }
958 1136 } else {
959 - $ip = $clientIp;
1137 + // range might be 255.255.*.* or 1.2.3.0-1.2.3.255
1138 + if (strpos($range, '*') !== false) { // a.b.*.* format
1139 + // Just convert to A-B format by setting * to 0 for A and 255 for B
1140 + $lower = str_replace('*', '0', $range);
1141 + $upper = str_replace('*', '255', $range);
1142 + $range = "$lower-$upper";
1143 + }
1144 +
1145 + if (strpos($range, '-') !== false) { // A-B format
1146 + list($lower, $upper) = explode('-', $range, 2);
1147 + $lower_dec = (float)sprintf("%u", ip2long($lower));
1148 + $upper_dec = (float)sprintf("%u", ip2long($upper));
1149 + $ip_dec = (float)sprintf("%u", ip2long($ip));
1150 + return (($ip_dec >= $lower_dec) && ($ip_dec <= $upper_dec));
1151 + }
1152 + return false;
960 1153 }
961 -
962 - return sanitize_text_field($ip);
963 1154 }
964 1155
965 1156 public static function fcal_sanitize_html($html)
966 1157 {
@@ -981,9 +1172,8 @@
981 1172 $tags['iframe'] = [
982 1173 'width' => [],
983 1174 'height' => [],
984 1175 'src' => [],
985 - 'srcdoc' => [],
986 1176 'title' => [],
987 1177 'frameborder' => [],
988 1178 'allow' => [],
989 1179 'class' => [],
@@ -991,9 +1181,11 @@
991 1181 'allowfullscreen' => [],
992 1182 'style' => [],
993 1183 ];
994 1184 //button
995 - $tags['button']['onclick'] = [];
1185 + $tags['button'] = [
1186 + 'onclick' => []
1187 + ];
996 1188
997 1189 //svg
998 1190 if (empty($tags['svg'])) {
999 1191 $svg_args = [
@@ -1212,9 +1404,10 @@
1212 1404
1213 1405 return $durationLookup;
1214 1406 }
1215 1407
1216 - public static function formatDuration($totalMinutes) {
1408 + public static function formatDuration($totalMinutes)
1409 + {
1217 1410 $days = floor($totalMinutes / 1440); // 1440 minutes in a day
1218 1411 $hours = floor(($totalMinutes % 1440) / 60);
1219 1412 $minutes = $totalMinutes % 60;
1220 1413
@@ -1521,12 +1714,23 @@
1521 1714 ],
1522 1715 [
1523 1716 'value' => 'hidden',
1524 1717 'label' => __('Hidden', 'fluent-booking')
1718 + ],
1719 + [
1720 + 'value' => 'terms-and-conditions',
1721 + 'label' => __('Terms & Conditions', 'fluent-booking')
1525 1722 ]
1526 1723 ]);
1527 1724 }
1528 1725
1726 + public static function getDefaultTermsAndConditions()
1727 + {
1728 + $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');
1729 +
1730 + return apply_filters('fluent_booking/default_terms_and_conditions', $termsAndConditions);
1731 + }
1732 +
1529 1733 public static function getDefaultEmailNotificationSettings()
1530 1734 {
1531 1735 $assetUrl = App::getInstance()['url.assets'];
1532 1736
@@ -1615,9 +1819,9 @@
1615 1819 'enabled' => true,
1616 1820 'title' => __('Booking Rescheduled by Organizer (email to Attendee)', 'fluent-booking'),
1617 1821 'email' => [
1618 1822 'subject' => 'Your booking was rescheduled with {{host.name}}',
1619 - '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}}</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)
1823 + '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)
1620 1824 ],
1621 1825 ],
1622 1826 'booking_request_host' => [
1623 1827 'enabled' => true,
@@ -1665,10 +1869,17 @@
1665 1869
1666 1870 return apply_filters('fluent_booking/confirm_and_reject_button_html', $html);
1667 1871 }
1668 1872
1669 - public static function getEditorShortCodes($calendarEvent = null, $isHtmlSupported = false)
1873 + public static function getIframeHtml()
1670 1874 {
1875 + $html = '<iframe id="fluentbooking" loading="lazy" height="700px" width="100%" style="min-width:320px;height:700px;" frameborder="0" src="##landing_page_url##"></iframe>';
1876 +
1877 + return apply_filters('fluent_booking/get_iframe_html', $html);
1878 + }
1879 +
1880 + public static function getEditorShortCodes($calendarEvent = null, $isHtmlSupported = false, $iframeHtml = '')
1881 + {
1671 1882 if (!$isHtmlSupported) {
1672 1883 $groups = [
1673 1884 'guest' => [
1674 1885 'title' => __('Attendee Data', 'fluent-booking'),
@@ -1673,15 +1884,16 @@
1673 1884 'guest' => [
1674 1885 'title' => __('Attendee Data', 'fluent-booking'),
1675 1886 'key' => 'guest',
1676 1887 'shortcodes' => [
1677 - '{{guest.first_name}}' => __('Guest First Name', 'fluent-booking'),
1678 - '{{guest.last_name}}' => __('Guest Last Name', 'fluent-booking'),
1679 - '{{guest.full_name}}' => __('Guest Full Name', 'fluent-booking'),
1680 - '{{guest.email}}' => __('Guest Email', 'fluent-booking'),
1681 - '{{guest.note}}' => __('Guest Note', 'fluent-booking'),
1682 - '{{booking.phone}}' => __('Guest Main Phone Number (if provided)', 'fluent-booking'),
1683 - '{{guest.timezone}}' => __('Guest Timezone', 'fluent-booking')
1888 + '{{guest.first_name}}' => __('Guest First Name', 'fluent-booking'),
1889 + '{{guest.last_name}}' => __('Guest Last Name', 'fluent-booking'),
1890 + '{{guest.full_name}}' => __('Guest Full Name', 'fluent-booking'),
1891 + '{{guest.email}}' => __('Guest Email', 'fluent-booking'),
1892 + '{{guest.note}}' => __('Guest Note', 'fluent-booking'),
1893 + '{{booking.phone}}' => __('Guest Main Phone Number (if provided)', 'fluent-booking'),
1894 + '{{guest.timezone}}' => __('Guest Timezone', 'fluent-booking'),
1895 + '{{guest.total_guest}}' => __('Total Guest Count', 'fluent-booking')
1684 1896 ]
1685 1897 ],
1686 1898 'booking' => [
1687 1899 'title' => __('Booking Data', 'fluent-booking'),
@@ -1686,27 +1898,41 @@
1686 1898 'booking' => [
1687 1899 'title' => __('Booking Data', 'fluent-booking'),
1688 1900 'key' => 'booking',
1689 1901 'shortcodes' => [
1690 - '{{booking.event_name}}' => __('Event Name', 'fluent-booking'),
1691 - '{{booking.description}}' => __('Event Description', 'fluent-booking'),
1692 - '{{booking.booking_title}}' => __('Booking Title', 'fluent-booking'),
1693 - '{{booking.additional_guests}}' => __('Additional Guests', 'fluent-booking'),
1694 - '{{booking.full_start_end_guest_timezone}}' => __('Full Start Date Time (with guest timezone)', 'fluent-booking'),
1695 - '{{booking.full_start_end_host_timezone}}' => __('Full Start Date Time (with host timezone)', 'fluent-booking'),
1696 - '{{booking.full_start_and_end_guest_timezone}}' => __('Full Start & End Date Time (with guest timezone)', 'fluent-booking'),
1697 - '{{booking.full_start_and_end_host_timezone}}' => __('Full Start & End Date Time (with host timezone)', 'fluent-booking'),
1698 - '{{booking.start_date_time}}' => __('Event Date Time (UTC)', 'fluent-booking'),
1699 - '{{booking.start_date_time_for_attendee}}' => __('Event Date Time (with attendee timezone)', 'fluent-booking'),
1700 - '{{booking.start_date_time_for_host}}' => __('Event Date Time (with host timezone)', 'fluent-booking'),
1701 - '{{booking.location_details_text}}' => __('Event Location Details', 'fluent-booking'),
1702 - '{{booking.cancel_reason}}' => __('Event Cancel Reason', 'fluent-booking'),
1703 - '{{booking.start_time_human_format}}' => __('Event Start Time (ex: 2 hours from now)', 'fluent-booking'),
1704 - '##booking.cancelation_url##' => __('Booking Cancellation URL', 'fluent-booking'),
1705 - '##booking.reschedule_url##' => __('Booking Reschedule URL', 'fluent-booking'),
1706 - '##booking.admin_booking_url##' => __('Booking Details Admin URL', 'fluent-booking'),
1707 - '{{booking.booking_hash}}' => __('Unique Booking Hash', 'fluent-booking'),
1708 - '{{booking.reschedule_reason}}' => __('Event Reschedule Reason', 'fluent-booking')
1902 + '{{booking.event_name}}' => __('Event Name', 'fluent-booking'),
1903 + '{{booking.description}}' => __('Event Description', 'fluent-booking'),
1904 + '{{booking.booking_title}}' => __('Booking Title', 'fluent-booking'),
1905 + '{{booking.additional_guests}}' => __('Additional Guests', 'fluent-booking'),
1906 + '{{booking.full_start_end_guest_timezone}}' => __('Full Start Date Time (with guest timezone)', 'fluent-booking'),
1907 + '{{booking.full_start_end_host_timezone}}' => __('Full Start Date Time (with host timezone)', 'fluent-booking'),
1908 + '{{booking.full_start_and_end_guest_timezone}}' => __('Full Start & End Date Time (with guest timezone)', 'fluent-booking'),
1909 + '{{booking.full_start_and_end_host_timezone}}' => __('Full Start & End Date Time (with host timezone)', 'fluent-booking'),
1910 + '{{booking.all_bookings_short_times_guest_timezone}}' => __('All Bookings Short Times (with guest timezone)', 'fluent-booking'),
1911 + '{{booking.all_bookings_short_times_host_timezone}}' => __('All Bookings Short Times (with host timezone)', 'fluent-booking'),
1912 + '{{booking.all_bookings_full_times_guest_timezone}}' => __('All Bookings Full Times (with guest timezone)', 'fluent-booking'),
1913 + '{{booking.all_bookings_full_times_host_timezone}}' => __('All Bookings Full Times (with host timezone)', 'fluent-booking'),
1914 + '{{booking.start_date_time}}' => __('Event Date Time (UTC)', 'fluent-booking'),
1915 + '{{booking.start_date_time_for_attendee}}' => __('Event Date Time (with attendee timezone)', 'fluent-booking'),
1916 + '{{booking.start_date_time_for_host}}' => __('Event Date Time (with host timezone)', 'fluent-booking'),
1917 + '{{booking.start_date_time_for_attendee.format.Y-m-d}}' => __('Event Date Time (with attendee timezone) (Ex: 2024-05-20)', 'fluent-booking'),
1918 + '{{booking.start_date_time_for_host.format.Y-m-d}}' => __('Event Date Time (with host timezone) (Ex: 2024-05-20)', 'fluent-booking'),
1919 + '{{booking.location_details_text}}' => __('Event Location Details', 'fluent-booking'),
1920 + '{{booking.cancel_reason}}' => __('Event Cancel Reason', 'fluent-booking'),
1921 + '{{booking.start_time_human_format}}' => __('Event Start Time (ex: 2 hours from now)', 'fluent-booking'),
1922 + '##booking.cancelation_url##' => __('Booking Cancellation URL', 'fluent-booking'),
1923 + '##booking.reschedule_url##' => __('Booking Reschedule URL', 'fluent-booking'),
1924 + '##booking.admin_booking_url##' => __('Booking Details Admin URL', 'fluent-booking'),
1925 + '{{booking.source_url}}' => __('Source URL', 'fluent-booking'),
1926 + '{{booking.utm_source}}' => __('UTM Source', 'fluent-booking'),
1927 + '{{booking.utm_medium}}' => __('UTM Medium', 'fluent-booking'),
1928 + '{{booking.utm_campaign}}' => __('UTM Campaign', 'fluent-booking'),
1929 + '{{booking.utm_term}}' => __('UTM Term', 'fluent-booking'),
1930 + '{{booking.utm_content}}' => __('UTM Content', 'fluent-booking'),
1931 + '{{booking.booking_hash}}' => __('Unique Booking Hash', 'fluent-booking'),
1932 + '{{booking.reschedule_reason}}' => __('Event Reschedule Reason', 'fluent-booking'),
1933 + '{{booking.previous_meeting_date_time_host_timezone}}' => __('Previous Meeting Date & Time (with host timezone)', 'fluent-booking'),
1934 + '{{booking.previous_meeting_date_time_guest_timezone}}' => __('Previous Meeting Date & Time (with guest timezone)', 'fluent-booking'),
1709 1935 ]
1710 1936 ],
1711 1937 'host' => [
1712 1938 'title' => __('Host Data', 'fluent-booking'),
@@ -1742,9 +1968,9 @@
1742 1968 '{{guest.email}}' => __('Guest Email', 'fluent-booking'),
1743 1969 '{{booking.phone}}' => __('Guest Main Phone Number (if provided)', 'fluent-booking'),
1744 1970 '{{guest.note}}' => __('Guest Note', 'fluent-booking'),
1745 1971 '{{guest.timezone}}' => __('Guest Timezone', 'fluent-booking'),
1746 - '{{guest.form_data_html}}' => __('Guest Form Submitted Data (HTML)', 'fluent-booking')
1972 + '{{guest.total_guest}}' => __('Total Guest Count', 'fluent-booking')
1747 1973 ]
1748 1974 ],
1749 1975 'booking' => [
1750 1976 'title' => __('Booking Data', 'fluent-booking'),
@@ -1749,27 +1975,41 @@
1749 1975 'booking' => [
1750 1976 'title' => __('Booking Data', 'fluent-booking'),
1751 1977 'key' => 'booking',
1752 1978 'shortcodes' => [
1753 - '{{booking.event_name}}' => __('Event Name', 'fluent-booking'),
1754 - '{{booking.description}}' => __('Event Description', 'fluent-booking'),
1755 - '{{booking.booking_title}}' => __('Booking Title', 'fluent-booking'),
1756 - '{{booking.additional_guests}}' => __('Additional Guests', 'fluent-booking'),
1757 - '{{booking.full_start_end_guest_timezone}}' => __('Full Start Date Time (with guest timezone)', 'fluent-booking'),
1758 - '{{booking.full_start_end_host_timezone}}' => __('Full Start Date Time (with host timezone)', 'fluent-booking'),
1759 - '{{booking.full_start_and_end_guest_timezone}}' => __('Full Start & End Date Time (with guest timezone)', 'fluent-booking'),
1760 - '{{booking.full_start_and_end_host_timezone}}' => __('Full Start & End Date Time (with host timezone)', 'fluent-booking'),
1761 - '{{booking.start_date_time}}' => __('Event Date Time (UTC)', 'fluent-booking'),
1762 - '{{booking.start_date_time_for_attendee}}' => __('Event Date time (with guest timezone)', 'fluent-booking'),
1763 - '{{booking.start_date_time_for_host}}' => __('Event Date time (with host timezone)', 'fluent-booking'),
1764 - '{{booking.location_details_html}}' => __('Event Location Details (HTML)', 'fluent-booking'),
1765 - '{{booking.cancel_reason}}' => __('Event Cancel Reason', 'fluent-booking'),
1766 - '{{booking.start_time_human_format}}' => __('Event Start Time (ex: 2 hours from now)', 'fluent-booking'),
1767 - '##booking.cancelation_url##' => __('Booking Cancellation URL', 'fluent-booking'),
1768 - '##booking.reschedule_url##' => __('Booking Reschedule URL', 'fluent-booking'),
1769 - '##booking.admin_booking_url##' => __('Booking Details Admin URL', 'fluent-booking'),
1770 - '{{booking.booking_hash}}' => __('Unique Booking Hash', 'fluent-booking'),
1771 - '{{booking.reschedule_reason}}' => __('Event Reschedule Reason', 'fluent-booking')
1979 + '{{booking.event_name}}' => __('Event Name', 'fluent-booking'),
1980 + '{{booking.description}}' => __('Event Description', 'fluent-booking'),
1981 + '{{booking.booking_title}}' => __('Booking Title', 'fluent-booking'),
1982 + '{{booking.additional_guests}}' => __('Additional Guests', 'fluent-booking'),
1983 + '{{booking.full_start_end_guest_timezone}}' => __('Full Start Date Time (with guest timezone)', 'fluent-booking'),
1984 + '{{booking.full_start_end_host_timezone}}' => __('Full Start Date Time (with host timezone)', 'fluent-booking'),
1985 + '{{booking.full_start_and_end_guest_timezone}}' => __('Full Start & End Date Time (with guest timezone)', 'fluent-booking'),
1986 + '{{booking.full_start_and_end_host_timezone}}' => __('Full Start & End Date Time (with host timezone)', 'fluent-booking'),
1987 + '{{booking.all_bookings_short_times_guest_timezone}}' => __('All Bookings Short Times (with guest timezone)', 'fluent-booking'),
1988 + '{{booking.all_bookings_short_times_host_timezone}}' => __('All Bookings Short Times (with host timezone)', 'fluent-booking'),
1989 + '{{booking.all_bookings_full_times_guest_timezone}}' => __('All Bookings Full Times (with guest timezone)', 'fluent-booking'),
1990 + '{{booking.all_bookings_full_times_host_timezone}}' => __('All Bookings Full Times (with host timezone)', 'fluent-booking'),
1991 + '{{booking.start_date_time}}' => __('Event Date Time (UTC)', 'fluent-booking'),
1992 + '{{booking.start_date_time_for_attendee}}' => __('Event Date Time (with guest timezone)', 'fluent-booking'),
1993 + '{{booking.start_date_time_for_host}}' => __('Event Date Time (with host timezone)', 'fluent-booking'),
1994 + '{{booking.start_date_time_for_attendee.format.Y-m-d}}' => __('Event Date Time (with attendee timezone) (Ex: 2024-05-20)', 'fluent-booking'),
1995 + '{{booking.start_date_time_for_host.format.Y-m-d}}' => __('Event Date Time (with host timezone) (Ex: 2024-05-20)', 'fluent-booking'),
1996 + '{{booking.location_details_html}}' => __('Event Location Details (HTML)', 'fluent-booking'),
1997 + '{{booking.cancel_reason}}' => __('Event Cancel Reason', 'fluent-booking'),
1998 + '{{booking.start_time_human_format}}' => __('Event Start Time (ex: 2 hours from now)', 'fluent-booking'),
1999 + '##booking.cancelation_url##' => __('Booking Cancellation URL', 'fluent-booking'),
2000 + '##booking.reschedule_url##' => __('Booking Reschedule URL', 'fluent-booking'),
2001 + '##booking.admin_booking_url##' => __('Booking Details Admin URL', 'fluent-booking'),
2002 + '{{booking.source_url}}' => __('Source URL', 'fluent-booking'),
2003 + '{{booking.utm_source}}' => __('UTM Source', 'fluent-booking'),
2004 + '{{booking.utm_medium}}' => __('UTM Medium', 'fluent-booking'),
2005 + '{{booking.utm_campaign}}' => __('UTM Campaign', 'fluent-booking'),
2006 + '{{booking.utm_term}}' => __('UTM Term', 'fluent-booking'),
2007 + '{{booking.utm_content}}' => __('UTM Content', 'fluent-booking'),
2008 + '{{booking.booking_hash}}' => __('Unique Booking Hash', 'fluent-booking'),
2009 + '{{booking.reschedule_reason}}' => __('Event Reschedule Reason', 'fluent-booking'),
2010 + '{{booking.previous_meeting_date_time_host_timezone}}' => __('Previous Meeting Date & Time (with host timezone)', 'fluent-booking'),
2011 + '{{booking.previous_meeting_date_time_guest_timezone}}' => __('Previous Meeting Date & Time (with guest timezone)', 'fluent-booking'),
1772 2012 ]
1773 2013 ],
1774 2014 'host' => [
1775 2015 'title' => __('Host Data', 'fluent-booking'),
@@ -1875,11 +2115,17 @@
1875 2115 return $raw_value;
1876 2116 }
1877 2117
1878 2118 $raw_value = base64_decode($raw_value, true); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
2119 + if ($raw_value === false) {
2120 + return false;
2121 + }
1879 2122
1880 2123 $method = 'aes-256-ctr';
1881 2124 $ivlen = openssl_cipher_iv_length($method);
2125 + if ($ivlen === false || strlen($raw_value) <= $ivlen) {
2126 + return false;
2127 + }
1882 2128 $iv = substr($raw_value, 0, $ivlen);
1883 2129
1884 2130 $raw_value = substr($raw_value, $ivlen);
1885 2131
@@ -1891,9 +2137,9 @@
1891 2137
1892 2138 $salt = (defined('LOGGED_IN_SALT') && '' !== LOGGED_IN_SALT) ? LOGGED_IN_SALT : 'this-is-a-fallback-salt-but-not-secure';
1893 2139
1894 2140 $value = openssl_decrypt($raw_value, $method, $key, 0, $iv);
1895 - if (!$value || substr($value, -strlen($salt)) !== $salt) {
2141 + if (!is_string($value) || substr($value, -strlen($salt)) !== $salt) {
1896 2142 return false;
1897 2143 }
1898 2144
1899 2145 return substr($value, 0, -strlen($salt));
@@ -1901,9 +2147,9 @@
1901 2147
1902 2148 public static function debugLog($data)
1903 2149 {
1904 2150 if (defined('FLUENT_BOOKING_DEBUG') && FLUENT_BOOKING_DEBUG) {
1905 - error_log(print_r($data, true));
2151 + error_log(print_r($data, true)); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log,WordPress.PHP.DevelopmentFunctions.error_log_print_r
1906 2152 }
1907 2153 }
1908 2154
1909 2155 public static function getGlobalSettings($settingsKey = null)
@@ -1930,9 +2176,9 @@
1930 2176 'notification_day' => 'mon',
1931 2177 'start_day' => 'sun',
1932 2178 'auto_cancel_timing' => '10',
1933 2179 'auto_complete_timing' => '60',
1934 - 'default_phone_country' => ''
2180 + 'default_country' => ''
1935 2181 ],
1936 2182 'time_format' => '12',
1937 2183 'theme' => 'system-default'
1938 2184 ];
@@ -1942,14 +2188,10 @@
1942 2188 if (empty($settings)) {
1943 2189 $settings = [];
1944 2190 }
1945 2191
1946 - $paymentSettings = get_option('fluent_booking_global_payment_settings', []);
2192 + $settings['payments'] = CurrenciesHelper::getGlobalCurrencySettings();
1947 2193
1948 - if ($paymentSettings) {
1949 - $settings['payments'] = $paymentSettings;
1950 - }
1951 -
1952 2194 $settings = wp_parse_args($settings, $defaults);
1953 2195
1954 2196 $emailSettings = $settings['emailing'];
1955 2197
@@ -2019,8 +2261,28 @@
2019 2261 $format = Arr::get($settings, 'time_format', '24');
2020 2262 return $format;
2021 2263 }
2022 2264
2265 + public static function getDefaultBookingFilters()
2266 + {
2267 + return apply_filters('fluent_booking/default_booking_filters', [
2268 + 'period' => 'upcoming',
2269 + 'author' => 'me', // me, all, calendar_id
2270 + 'event' => 'all',
2271 + 'event_type' => 'all'
2272 + ]);
2273 + }
2274 +
2275 + public static function getDefaultPaginations()
2276 + {
2277 + return apply_filters('fluent_booking/default_paginations', [
2278 + 'bookings' => 10,
2279 + 'calendars' => 10,
2280 + 'coupons' => 10,
2281 + 'availabilities' => 10
2282 + ]);
2283 + }
2284 +
2023 2285 public static function getVerifiedSenders()
2024 2286 {
2025 2287 $verifiedSenders = [];
2026 2288 if (defined('FLUENTMAIL')) {
@@ -2096,9 +2358,9 @@
2096 2358 }
2097 2359 return apply_filters('fluent_booking/author_photo', get_avatar_url($id_or_email), $args);
2098 2360 }
2099 2361
2100 - public static function getPrefSettins($cached = true)
2362 + public static function getPrefSettings($cached = true)
2101 2363 {
2102 2364 static $pref = null;
2103 2365
2104 2366 if ($cached && $pref) {
@@ -2110,8 +2372,11 @@
2110 2372 'enabled' => 'no',
2111 2373 'slug' => 'my-bookings',
2112 2374 'render_type' => 'standalone',
2113 2375 'page_id' => ''
2376 + ],
2377 + 'coupon' => [
2378 + 'enabled' => 'no'
2114 2379 ]
2115 2380 ];
2116 2381
2117 2382 $storedSettings = get_option('fluent_booking_modules', []);
@@ -2124,6 +2389,99 @@
2124 2389
2125 2390 $pref = $settings;
2126 2391
2127 2392 return $settings;
2393 + }
2394 +
2395 + public static function getPrefSettins($cached = true)
2396 + {
2397 + return self::getPrefSettings($cached);
2398 + }
2399 +
2400 + public static function getFeatures()
2401 + {
2402 + return apply_filters('fluent_booking/get_features', [
2403 + 'has_fluentcrm' => defined('FLUENTCRM'),
2404 + 'has_fluentsmtp' => defined('FLUENTMAIL'),
2405 + 'has_fluentform' => defined('FLUENTFORM'),
2406 + 'has_fluentboards' => defined('FLUENT_BOARDS'),
2407 + 'has_fluentcart' => defined('FLUENTCART_VERSION')
2408 + ]);
2409 + }
2410 +
2411 + public static function getActiveThemeName()
2412 + {
2413 + $ins = get_option('_fb_ins_by');
2414 +
2415 + if ($ins) {
2416 + return sanitize_text_field($ins);
2417 + }
2418 +
2419 + return get_option('template');
2420 + }
2421 +
2422 + /**
2423 + * Per-IP fixed-window rate limiter for public AJAX/REST endpoints.
2424 + *
2425 + * @param string $action Action name (e.g. apply_coupon, schedule_meeting).
2426 + * @param int $limit Max requests per window.
2427 + * @param int $window Window in seconds.
2428 + * @return bool True if under the limit (and the count was incremented),
2429 + * false if over.
2430 + */
2431 + public static function checkRateLimit($action, $limit, $window = 60)
2432 + {
2433 + $args = apply_filters('fluent_booking/public_ajax_ratelimit', [
2434 + 'limit' => $limit,
2435 + 'window' => $window,
2436 + ], $action);
2437 +
2438 + $limit = max(1, (int) (isset($args['limit']) ? $args['limit'] : $limit));
2439 + $window = max(1, (int) (isset($args['window']) ? $args['window'] : $window));
2440 +
2441 + $key = 'fcal_ratelimit_' . $action . '_' . md5(self::getIp());
2442 + $count = (int) get_transient($key);
2443 +
2444 + if ($count >= $limit) {
2445 + return false;
2446 + }
2447 +
2448 + set_transient($key, $count + 1, $window);
2449 +
2450 + return true;
2451 + }
2452 +
2453 + /**
2454 + * Run a callback inside a database transaction, re-throwing on failure so
2455 + * the caller decides how to report it.
2456 + *
2457 + * \Throwable, not \Exception: a TypeError is an Error, and an
2458 + * Exception-only catch would leave the transaction open.
2459 + *
2460 + * Database writes only — a hook fired in here would hold the callback's
2461 + * rows locked for the length of a listener's outbound request.
2462 + *
2463 + * @param callable $callback
2464 + *
2465 + * @return mixed
2466 + *
2467 + * @throws \Throwable after the rollback
2468 + */
2469 + public static function dbTransaction($callback)
2470 + {
2471 + $db = App::getInstance('db');
2472 +
2473 + $db->beginTransaction();
2474 +
2475 + try {
2476 + $result = $callback();
2477 +
2478 + $db->commit();
2479 +
2480 + return $result;
2481 + } catch (\Throwable $e) {
2482 + $db->rollBack();
2483 +
2484 + throw $e;
2485 + }
2128 2486 }
2129 2487 }