| @@ -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 | |
| @@ -1513,12 +1706,31 @@ | ||
| 1513 | 1706 | ], |
| 1514 | 1707 | [ |
| 1515 | 1708 | 'value' => 'date', |
| 1516 | 1709 | 'label' => __('Date', 'fluent-booking') |
| 1710 | + ], | |
| 1711 | + [ | |
| 1712 | + 'value' => 'file', | |
| 1713 | + 'label' => __('File', 'fluent-booking') | |
| 1714 | + ], | |
| 1715 | + [ | |
| 1716 | + 'value' => 'hidden', | |
| 1717 | + 'label' => __('Hidden', 'fluent-booking') | |
| 1718 | + ], | |
| 1719 | + [ | |
| 1720 | + 'value' => 'terms-and-conditions', | |
| 1721 | + 'label' => __('Terms & Conditions', 'fluent-booking') | |
| 1517 | 1722 | ] |
| 1518 | 1723 | ]); |
| 1519 | 1724 | } |
| 1520 | 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 | + | |
| 1521 | 1733 | public static function getDefaultEmailNotificationSettings() |
| 1522 | 1734 | { |
| 1523 | 1735 | $assetUrl = App::getInstance()['url.assets']; |
| 1524 | 1736 | |
| @@ -1607,9 +1819,9 @@ | ||
| 1607 | 1819 | 'enabled' => true, |
| 1608 | 1820 | 'title' => __('Booking Rescheduled by Organizer (email to Attendee)', 'fluent-booking'), |
| 1609 | 1821 | 'email' => [ |
| 1610 | 1822 | 'subject' => 'Your booking was rescheduled with {{host.name}}', |
| 1611 | - '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) | |
| 1612 | 1824 | ], |
| 1613 | 1825 | ], |
| 1614 | 1826 | 'booking_request_host' => [ |
| 1615 | 1827 | 'enabled' => true, |
| @@ -1657,10 +1869,17 @@ | ||
| 1657 | 1869 | |
| 1658 | 1870 | return apply_filters('fluent_booking/confirm_and_reject_button_html', $html); |
| 1659 | 1871 | } |
| 1660 | 1872 | |
| 1661 | - public static function getEditorShortCodes($calendarEvent = null, $isHtmlSupported = false) | |
| 1873 | + public static function getIframeHtml() | |
| 1662 | 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 | + { | |
| 1663 | 1882 | if (!$isHtmlSupported) { |
| 1664 | 1883 | $groups = [ |
| 1665 | 1884 | 'guest' => [ |
| 1666 | 1885 | 'title' => __('Attendee Data', 'fluent-booking'), |
| @@ -1665,15 +1884,16 @@ | ||
| 1665 | 1884 | 'guest' => [ |
| 1666 | 1885 | 'title' => __('Attendee Data', 'fluent-booking'), |
| 1667 | 1886 | 'key' => 'guest', |
| 1668 | 1887 | 'shortcodes' => [ |
| 1669 | - '{{guest.first_name}}' => __('Guest First Name', 'fluent-booking'), | |
| 1670 | - '{{guest.last_name}}' => __('Guest Last Name', 'fluent-booking'), | |
| 1671 | - '{{guest.full_name}}' => __('Guest Full Name', 'fluent-booking'), | |
| 1672 | - '{{guest.email}}' => __('Guest Email', 'fluent-booking'), | |
| 1673 | - '{{guest.note}}' => __('Guest Note', 'fluent-booking'), | |
| 1674 | - '{{booking.phone}}' => __('Guest Main Phone Number (if provided)', 'fluent-booking'), | |
| 1675 | - '{{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') | |
| 1676 | 1896 | ] |
| 1677 | 1897 | ], |
| 1678 | 1898 | 'booking' => [ |
| 1679 | 1899 | 'title' => __('Booking Data', 'fluent-booking'), |
| @@ -1678,27 +1898,41 @@ | ||
| 1678 | 1898 | 'booking' => [ |
| 1679 | 1899 | 'title' => __('Booking Data', 'fluent-booking'), |
| 1680 | 1900 | 'key' => 'booking', |
| 1681 | 1901 | 'shortcodes' => [ |
| 1682 | - '{{booking.event_name}}' => __('Event Name', 'fluent-booking'), | |
| 1683 | - '{{booking.description}}' => __('Event Description', 'fluent-booking'), | |
| 1684 | - '{{booking.booking_title}}' => __('Booking Title', 'fluent-booking'), | |
| 1685 | - '{{booking.additional_guests}}' => __('Additional Guests', 'fluent-booking'), | |
| 1686 | - '{{booking.full_start_end_guest_timezone}}' => __('Full Start Date Time (with guest timezone)', 'fluent-booking'), | |
| 1687 | - '{{booking.full_start_end_host_timezone}}' => __('Full Start Date Time (with host timezone)', 'fluent-booking'), | |
| 1688 | - '{{booking.full_start_and_end_guest_timezone}}' => __('Full Start & End Date Time (with guest timezone)', 'fluent-booking'), | |
| 1689 | - '{{booking.full_start_and_end_host_timezone}}' => __('Full Start & End Date Time (with host timezone)', 'fluent-booking'), | |
| 1690 | - '{{booking.start_date_time}}' => __('Event Date Time (UTC)', 'fluent-booking'), | |
| 1691 | - '{{booking.start_date_time_for_attendee}}' => __('Event Date Time (with attendee timezone)', 'fluent-booking'), | |
| 1692 | - '{{booking.start_date_time_for_host}}' => __('Event Date Time (with host timezone)', 'fluent-booking'), | |
| 1693 | - '{{booking.location_details_text}}' => __('Event Location Details', 'fluent-booking'), | |
| 1694 | - '{{booking.cancel_reason}}' => __('Event Cancel Reason', 'fluent-booking'), | |
| 1695 | - '{{booking.start_time_human_format}}' => __('Event Start Time (ex: 2 hours from now)', 'fluent-booking'), | |
| 1696 | - '##booking.cancelation_url##' => __('Booking Cancellation URL', 'fluent-booking'), | |
| 1697 | - '##booking.reschedule_url##' => __('Booking Reschedule URL', 'fluent-booking'), | |
| 1698 | - '##booking.admin_booking_url##' => __('Booking Details Admin URL', 'fluent-booking'), | |
| 1699 | - '{{booking.booking_hash}}' => __('Unique Booking Hash', 'fluent-booking'), | |
| 1700 | - '{{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'), | |
| 1701 | 1935 | ] |
| 1702 | 1936 | ], |
| 1703 | 1937 | 'host' => [ |
| 1704 | 1938 | 'title' => __('Host Data', 'fluent-booking'), |
| @@ -1734,9 +1968,9 @@ | ||
| 1734 | 1968 | '{{guest.email}}' => __('Guest Email', 'fluent-booking'), |
| 1735 | 1969 | '{{booking.phone}}' => __('Guest Main Phone Number (if provided)', 'fluent-booking'), |
| 1736 | 1970 | '{{guest.note}}' => __('Guest Note', 'fluent-booking'), |
| 1737 | 1971 | '{{guest.timezone}}' => __('Guest Timezone', 'fluent-booking'), |
| 1738 | - '{{guest.form_data_html}}' => __('Guest Form Submitted Data (HTML)', 'fluent-booking') | |
| 1972 | + '{{guest.total_guest}}' => __('Total Guest Count', 'fluent-booking') | |
| 1739 | 1973 | ] |
| 1740 | 1974 | ], |
| 1741 | 1975 | 'booking' => [ |
| 1742 | 1976 | 'title' => __('Booking Data', 'fluent-booking'), |
| @@ -1741,27 +1975,41 @@ | ||
| 1741 | 1975 | 'booking' => [ |
| 1742 | 1976 | 'title' => __('Booking Data', 'fluent-booking'), |
| 1743 | 1977 | 'key' => 'booking', |
| 1744 | 1978 | 'shortcodes' => [ |
| 1745 | - '{{booking.event_name}}' => __('Event Name', 'fluent-booking'), | |
| 1746 | - '{{booking.description}}' => __('Event Description', 'fluent-booking'), | |
| 1747 | - '{{booking.booking_title}}' => __('Booking Title', 'fluent-booking'), | |
| 1748 | - '{{booking.additional_guests}}' => __('Additional Guests', 'fluent-booking'), | |
| 1749 | - '{{booking.full_start_end_guest_timezone}}' => __('Full Start Date Time (with guest timezone)', 'fluent-booking'), | |
| 1750 | - '{{booking.full_start_end_host_timezone}}' => __('Full Start Date Time (with host timezone)', 'fluent-booking'), | |
| 1751 | - '{{booking.full_start_and_end_guest_timezone}}' => __('Full Start & End Date Time (with guest timezone)', 'fluent-booking'), | |
| 1752 | - '{{booking.full_start_and_end_host_timezone}}' => __('Full Start & End Date Time (with host timezone)', 'fluent-booking'), | |
| 1753 | - '{{booking.start_date_time}}' => __('Event Date Time (UTC)', 'fluent-booking'), | |
| 1754 | - '{{booking.start_date_time_for_attendee}}' => __('Event Date time (with guest timezone)', 'fluent-booking'), | |
| 1755 | - '{{booking.start_date_time_for_host}}' => __('Event Date time (with host timezone)', 'fluent-booking'), | |
| 1756 | - '{{booking.location_details_html}}' => __('Event Location Details (HTML)', 'fluent-booking'), | |
| 1757 | - '{{booking.cancel_reason}}' => __('Event Cancel Reason', 'fluent-booking'), | |
| 1758 | - '{{booking.start_time_human_format}}' => __('Event Start Time (ex: 2 hours from now)', 'fluent-booking'), | |
| 1759 | - '##booking.cancelation_url##' => __('Booking Cancellation URL', 'fluent-booking'), | |
| 1760 | - '##booking.reschedule_url##' => __('Booking Reschedule URL', 'fluent-booking'), | |
| 1761 | - '##booking.admin_booking_url##' => __('Booking Details Admin URL', 'fluent-booking'), | |
| 1762 | - '{{booking.booking_hash}}' => __('Unique Booking Hash', 'fluent-booking'), | |
| 1763 | - '{{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'), | |
| 1764 | 2012 | ] |
| 1765 | 2013 | ], |
| 1766 | 2014 | 'host' => [ |
| 1767 | 2015 | 'title' => __('Host Data', 'fluent-booking'), |
| @@ -1867,11 +2115,17 @@ | ||
| 1867 | 2115 | return $raw_value; |
| 1868 | 2116 | } |
| 1869 | 2117 | |
| 1870 | 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 | + } | |
| 1871 | 2122 | |
| 1872 | 2123 | $method = 'aes-256-ctr'; |
| 1873 | 2124 | $ivlen = openssl_cipher_iv_length($method); |
| 2125 | + if ($ivlen === false || strlen($raw_value) <= $ivlen) { | |
| 2126 | + return false; | |
| 2127 | + } | |
| 1874 | 2128 | $iv = substr($raw_value, 0, $ivlen); |
| 1875 | 2129 | |
| 1876 | 2130 | $raw_value = substr($raw_value, $ivlen); |
| 1877 | 2131 | |
| @@ -1883,9 +2137,9 @@ | ||
| 1883 | 2137 | |
| 1884 | 2138 | $salt = (defined('LOGGED_IN_SALT') && '' !== LOGGED_IN_SALT) ? LOGGED_IN_SALT : 'this-is-a-fallback-salt-but-not-secure'; |
| 1885 | 2139 | |
| 1886 | 2140 | $value = openssl_decrypt($raw_value, $method, $key, 0, $iv); |
| 1887 | - if (!$value || substr($value, -strlen($salt)) !== $salt) { | |
| 2141 | + if (!is_string($value) || substr($value, -strlen($salt)) !== $salt) { | |
| 1888 | 2142 | return false; |
| 1889 | 2143 | } |
| 1890 | 2144 | |
| 1891 | 2145 | return substr($value, 0, -strlen($salt)); |
| @@ -1893,9 +2147,9 @@ | ||
| 1893 | 2147 | |
| 1894 | 2148 | public static function debugLog($data) |
| 1895 | 2149 | { |
| 1896 | 2150 | if (defined('FLUENT_BOOKING_DEBUG') && FLUENT_BOOKING_DEBUG) { |
| 1897 | - 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 | |
| 1898 | 2152 | } |
| 1899 | 2153 | } |
| 1900 | 2154 | |
| 1901 | 2155 | public static function getGlobalSettings($settingsKey = null) |
| @@ -1922,9 +2176,9 @@ | ||
| 1922 | 2176 | 'notification_day' => 'mon', |
| 1923 | 2177 | 'start_day' => 'sun', |
| 1924 | 2178 | 'auto_cancel_timing' => '10', |
| 1925 | 2179 | 'auto_complete_timing' => '60', |
| 1926 | - 'default_phone_country' => '' | |
| 2180 | + 'default_country' => '' | |
| 1927 | 2181 | ], |
| 1928 | 2182 | 'time_format' => '12', |
| 1929 | 2183 | 'theme' => 'system-default' |
| 1930 | 2184 | ]; |
| @@ -1934,14 +2188,10 @@ | ||
| 1934 | 2188 | if (empty($settings)) { |
| 1935 | 2189 | $settings = []; |
| 1936 | 2190 | } |
| 1937 | 2191 | |
| 1938 | - $paymentSettings = get_option('fluent_booking_global_payment_settings', []); | |
| 2192 | + $settings['payments'] = CurrenciesHelper::getGlobalCurrencySettings(); | |
| 1939 | 2193 | |
| 1940 | - if ($paymentSettings) { | |
| 1941 | - $settings['payments'] = $paymentSettings; | |
| 1942 | - } | |
| 1943 | - | |
| 1944 | 2194 | $settings = wp_parse_args($settings, $defaults); |
| 1945 | 2195 | |
| 1946 | 2196 | $emailSettings = $settings['emailing']; |
| 1947 | 2197 | |
| @@ -2011,8 +2261,28 @@ | ||
| 2011 | 2261 | $format = Arr::get($settings, 'time_format', '24'); |
| 2012 | 2262 | return $format; |
| 2013 | 2263 | } |
| 2014 | 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 | + | |
| 2015 | 2285 | public static function getVerifiedSenders() |
| 2016 | 2286 | { |
| 2017 | 2287 | $verifiedSenders = []; |
| 2018 | 2288 | if (defined('FLUENTMAIL')) { |
| @@ -2088,9 +2358,9 @@ | ||
| 2088 | 2358 | } |
| 2089 | 2359 | return apply_filters('fluent_booking/author_photo', get_avatar_url($id_or_email), $args); |
| 2090 | 2360 | } |
| 2091 | 2361 | |
| 2092 | - public static function getPrefSettins($cached = true) | |
| 2362 | + public static function getPrefSettings($cached = true) | |
| 2093 | 2363 | { |
| 2094 | 2364 | static $pref = null; |
| 2095 | 2365 | |
| 2096 | 2366 | if ($cached && $pref) { |
| @@ -2102,8 +2372,11 @@ | ||
| 2102 | 2372 | 'enabled' => 'no', |
| 2103 | 2373 | 'slug' => 'my-bookings', |
| 2104 | 2374 | 'render_type' => 'standalone', |
| 2105 | 2375 | 'page_id' => '' |
| 2376 | + ], | |
| 2377 | + 'coupon' => [ | |
| 2378 | + 'enabled' => 'no' | |
| 2106 | 2379 | ] |
| 2107 | 2380 | ]; |
| 2108 | 2381 | |
| 2109 | 2382 | $storedSettings = get_option('fluent_booking_modules', []); |
| @@ -2116,6 +2389,99 @@ | ||
| 2116 | 2389 | |
| 2117 | 2390 | $pref = $settings; |
| 2118 | 2391 | |
| 2119 | 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 | + } | |
| 2120 | 2486 | } |
| 2121 | 2487 | } |