where('id', '!=', $exceptId)->first(); } else { $exist = Calendar::where('slug', $slug)->first(); } if ($exist) { return false; } } if (is_numeric($slug)) { return false; } // check if $slug has any special characters or any space. We will only allow alpha-numeric values return preg_match('/^[a-zA-Z0-9_-]+$/', $slug); } public static function isEventSlugAvailable($slug, $checkDb = true, $calendarId = null, $exceptId = null) { if (in_array($slug, self::$reserved)) { return false; } if (strlen($slug) < 4) { return false; } if ($checkDb) { $eventQuery = CalendarSlot::where('slug', $slug); if ($exceptId) { $eventQuery->where('id', '!=', $exceptId); } if ($calendarId) { $eventQuery->where('calendar_id', $calendarId); } $exist = $eventQuery->first(); if ($exist) { return false; } } if (is_numeric($slug)) { return false; } // check if $slug has any special characters or any space. We will only allow alpha-numeric values return preg_match('/^[a-zA-Z0-9_-]+$/', $slug); } public static function getAppBaseUrl($extension = '') { return apply_filters('fluent_booking/admin_base_url', admin_url('admin.php?page=fluent-booking#/' . $extension), $extension); } public static function getAdminBookingUrl($bookingId) { return self::getAppBaseUrl('scheduled-events?booking_id=' . $bookingId); } /** * Build a spec-compliant "Upgrade to Pro" URL. * * Follows the shared Fluent* UTM spec: * utm_source = fluent-booking (fixed vocabulary, never the wp.org slug) * utm_medium = free_plugin | pro_plugin (acquisition vs cross-sell) * utm_campaign= upgrade_pro (override for xsell_ / license_* ) * utm_content = the exact placement, e.g. feature_lock_team_calendar, upgrade_page * utm_term = plugin version that generated the link * utm_id = promo id, blank normally (omit unless passed) * * @param string $content The utm_content placement. * @param array $overrides Override any utm_* param (e.g. utm_campaign for cross-sell). * @return string */ public static function getUpgradeUrl($content = 'upgrade_page', $overrides = []) { $baseUrl = apply_filters( 'fluent_booking/pro_upgrade_base_url', 'https://fluentbooking.com/pricing/' ); $params = wp_parse_args($overrides, [ 'utm_source' => 'fluent-booking', 'utm_medium' => defined('FLUENT_BOOKING_PRO_VERSION') ? 'pro_plugin' : 'free_plugin', 'utm_campaign' => 'upgrade_pro', 'utm_content' => $content, 'utm_term' => FLUENT_BOOKING_VERSION, ]); // Drop any blank params (e.g. an unset utm_id) so they never hit the URL. $params = array_filter($params, function ($value) { return $value !== '' && $value !== null; }); return add_query_arg($params, $baseUrl); } public static function getNextBookingGroup() { $lastBooking = Booking::orderBy('group_id', 'desc')->first(['group_id']); if ($lastBooking) { return $lastBooking->group_id + 1; } return 1; } public static function getNextIndex() { static $index = 0; $index += 1; return $index; } public static function getGlobalPaymentSettings() { static $settings; if ($settings) { return $settings; } $settings = get_option('fluent_booking_global_payment_settings', []); return $settings; } public static function isPaymentEnabled($calendarEvent = null) { $settings = CurrenciesHelper::getGlobalCurrencySettings(); if (Arr::get($settings, 'is_active') == 'yes') { return true; } if ($calendarEvent) { if ($calendarEvent->type != 'paid') { return false; } $exist = Meta::where('object_type', 'calendar_slot') ->where('object_id', $calendarEvent->id) ->where('key', 'payment_settings') ->first(); return $exist && $exist->value && Arr::get($exist->value, 'enabled') == 'yes'; } return false; } public static function isPaymentConfigured($method = 'stripe') { $settings = get_option('fluent_booking_payment_settings_' . $method, []); return Arr::get($settings, 'is_active', 'no') == 'yes'; } /** * Sanitize form inputs recursively. * * @param $input * * @return mixed $input */ public static function fluentbookingSanitizer($input, $attribute = null, $fields = []) { if (is_string($input)) { $element = Arr::get($fields, $attribute . '.element'); if (in_array($element, ['post_content', 'rich_text_input'])) { return wp_kses_post($input); } elseif ('textarea' === $element) { $input = sanitize_textarea_field($input); } elseif ('input_email' === $element) { $input = strtolower(sanitize_text_field($input)); } elseif ('input_url' === $element) { $input = sanitize_url($input); } else { $input = sanitize_text_field($input); } } elseif (is_array($input)) { foreach ($input as $key => &$value) { $attribute = $attribute ? $attribute . '[' . $key . ']' : $key; $value = self::fluentbookingSanitizer($value, $attribute, $fields); $attribute = null; } } return $input; } public static function getMeta($group, $objectId, $key, $withModel = false) { $meta = Meta::where('object_type', $group) ->where('object_id', $objectId) ->where('key', $key) ->first(); if ($meta) { if ($withModel) { return $meta; } return $meta->value; } return null; } public static function updateMeta($group, $objectId, $key, $value) { $meta = self::getMeta($group, $objectId, $key, true); if ($meta) { $meta->value = $value; $meta->save(); return $meta; } return Meta::create([ 'object_type' => $group, 'key' => $key, 'object_id' => $objectId, 'value' => $value ]); } public static function deleteMeta($group, $objectId, $key) { return Meta::where('object_type', $group) ->where('object_id', $objectId) ->where('key', $key) ->delete(); } public static function getBookingMeta($eventId, $metaKey, $withModel = false) { $bookingMeta = BookingMeta::where('booking_id', $eventId) ->where('meta_key', $metaKey) ->first(); if ($bookingMeta) { return $withModel ? $bookingMeta : $bookingMeta->value; } return null; } public static function updateBookingMeta($eventId, $metaKey, $value) { $bookingMeta = self::getBookingMeta($eventId, $metaKey, true); if ($bookingMeta) { $bookingMeta->value = $value; $bookingMeta->save(); return $bookingMeta; } return BookingMeta::create([ 'booking_id' => $eventId, 'meta_key' => $metaKey, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key 'value' => $value ]); } public static function getUserDisplayName($userId = null) { if (!$userId) { $userId = get_current_user_id(); } if (!$userId) { return ''; } $user = get_user_by('ID', $userId); return self::getDisplayNameFromUser($user); } public static function getDisplayNameFromUser($user) { if (!$user) { return ''; } $firstName = is_object($user) ? ($user->first_name ?? '') : ''; $lastName = is_object($user) ? ($user->last_name ?? '') : ''; $name = trim($firstName . ' ' . $lastName); if ($name !== '') { return $name; } $displayName = is_object($user) ? ($user->display_name ?? '') : ''; return (string) $displayName; } public static function getUserEmail($userId = null) { $userId = $userId ?: get_current_user_id(); if (!$userId) { return ''; } $user = get_user_by('ID', $userId); return $user->user_email; } public static function excerpt($text, $max_length = 160) { // Strip HTML tags and convert entities to their corresponding characters $text = html_entity_decode(wp_strip_all_tags($text)); // Remove any line breaks, tabs, or extra whitespace $text = preg_replace('/\s+/', ' ', trim($text)); if (mb_strlen($text) > $max_length) { $text = mb_substr($text, 0, $max_length); $text = preg_replace('/\s+\S+$/', '', $text) . '...'; } return $text; } public static function generateSlotSlug($default, $calendar) { $original = sanitize_title($default, $default, 'display'); $default = $original; $counter = 1; while (CalendarSlot::where('calendar_id', $calendar->id)->where('slug', $default)->first()) { $default = $original . '-' . $counter; $counter += 1; } return apply_filters('fluent_booking/slot_slug', $default, $original); } public static function getIp($defalt = '127.0.0.1') { static $ipAddress; if ($ipAddress) { return $ipAddress; } if (empty($_SERVER['REMOTE_ADDR'])) { // It's a local cli request return $defalt; } $ipAddress = ''; $serverData = $_SERVER; $HTTP_CF_CONNECTING_IP = Arr::get($serverData, 'HTTP_CF_CONNECTING_IP'); $RemoteAddr = Arr::get($serverData, 'REMOTE_ADDR'); $clientIp = Arr::get($serverData, 'HTTP_CLIENT_IP'); $HTTP_X_FORWARDED_FOR = Arr::get($serverData, 'HTTP_X_FORWARDED_FOR'); if ($HTTP_CF_CONNECTING_IP) { //If it's a valid Cloudflare request if (self::isCfIp($RemoteAddr)) { //Use the CF-Connecting-IP header. $ipAddress = $HTTP_CF_CONNECTING_IP; } else { //If it isn't valid, then use REMOTE_ADDR. $ipAddress = $RemoteAddr; } } else if ($RemoteAddr == '127.0.0.1') { // most probably it's local reverse proxy if ($clientIp) { $ipAddress = $clientIp; } else if ($HTTP_X_FORWARDED_FOR) { $ipAddress = (string)rest_is_ip_address(trim(current(preg_split('/,/', sanitize_text_field($HTTP_X_FORWARDED_FOR))))); } } if (!$ipAddress) { $ipAddress = $RemoteAddr; } $ipAddress = preg_replace('/^(\d+\.\d+\.\d+\.\d+):\d+$/', '\1', $ipAddress); $ipAddress = apply_filters('fluent_booking/user_ip', $ipAddress, []); $ipAddress = sanitize_text_field(wp_unslash($ipAddress)); return $ipAddress; } /** * Valid E.164 number: 7-15 significant digits. * * @param string $phone * @return bool */ public static function isValidPhoneNumber($phone) { if (!apply_filters('fluent_booking/enforce_phone_validation', true)) { return true; } $phone = trim((string)$phone); if ($phone === '') { return false; } if (!preg_match('/^\+?[0-9\s().\-]+$/', $phone)) { return false; } $digitCount = strlen(preg_replace('/\D/', '', $phone)); $min = (int)apply_filters('fluent_booking/phone_min_digits', 7); $max = (int)apply_filters('fluent_booking/phone_max_digits', 15); return $digitCount >= $min && $digitCount <= $max; } private static function isCfIp($ip = '') { if (!$ip) { $serverData = $_SERVER; $REMOTE_ADDR = Arr::get($serverData, 'REMOTE_ADDR'); $ip = $REMOTE_ADDR; } $cloudflareIPRanges = array( '173.245.48.0/20', '103.21.244.0/22', '103.22.200.0/22', '103.31.4.0/22', '141.101.64.0/18', '108.162.192.0/18', '190.93.240.0/20', '188.114.96.0/20', '197.234.240.0/22', '198.41.128.0/17', '162.158.0.0/15', '104.16.0.0/13', '104.24.0.0/14', '172.64.0.0/13', '131.0.72.0/22', ); //Make sure that the request came via Cloudflare. foreach ($cloudflareIPRanges as $range) { //Use the ip_in_range function from Joomla. if (self::ipInRange($ip, $range)) { //IP is valid. Belongs to Cloudflare. return true; } } return false; } private static function ipInRange($ip, $range) { if (strpos($range, '/') !== false) { // $range is in IP/NETMASK format list($range, $netmask) = explode('/', $range, 2); if (strpos($netmask, '.') !== false) { // $netmask is a 255.255.0.0 format $netmask = str_replace('*', '0', $netmask); $netmask_dec = ip2long($netmask); return ((ip2long($ip) & $netmask_dec) == (ip2long($range) & $netmask_dec)); } else { // $netmask is a CIDR size block // fix the range argument $x = explode('.', $range); while (count($x) < 4) $x[] = '0'; list($a, $b, $c, $d) = $x; $range = sprintf("%u.%u.%u.%u", empty($a) ? '0' : $a, empty($b) ? '0' : $b, empty($c) ? '0' : $c, empty($d) ? '0' : $d); $range_dec = ip2long($range); $ip_dec = ip2long($ip); # Strategy 1 - Create the netmask with 'netmask' 1s and then fill it to 32 with 0s #$netmask_dec = bindec(str_pad('', $netmask, '1') . str_pad('', 32-$netmask, '0')); # Strategy 2 - Use math to create it $wildcard_dec = pow(2, (32 - $netmask)) - 1; $netmask_dec = ~$wildcard_dec; return (($ip_dec & $netmask_dec) == ($range_dec & $netmask_dec)); } } else { // range might be 255.255.*.* or 1.2.3.0-1.2.3.255 if (strpos($range, '*') !== false) { // a.b.*.* format // Just convert to A-B format by setting * to 0 for A and 255 for B $lower = str_replace('*', '0', $range); $upper = str_replace('*', '255', $range); $range = "$lower-$upper"; } if (strpos($range, '-') !== false) { // A-B format list($lower, $upper) = explode('-', $range, 2); $lower_dec = (float)sprintf("%u", ip2long($lower)); $upper_dec = (float)sprintf("%u", ip2long($upper)); $ip_dec = (float)sprintf("%u", ip2long($ip)); return (($ip_dec >= $lower_dec) && ($ip_dec <= $upper_dec)); } return false; } } public static function fcal_sanitize_html($html) { if (!$html) { return $html; } // Return $html if it's just a plain text if (!preg_match('/<[^>]*>/', $html)) { return $html; } $tags = wp_kses_allowed_html('post'); $tags['style'] = [ 'types' => [], ]; // iframe $tags['iframe'] = [ 'width' => [], 'height' => [], 'src' => [], 'title' => [], 'frameborder' => [], 'allow' => [], 'class' => [], 'id' => [], 'allowfullscreen' => [], 'style' => [], ]; //button $tags['button'] = [ 'onclick' => [] ]; //svg if (empty($tags['svg'])) { $svg_args = [ 'svg' => [ 'class' => true, 'aria-hidden' => true, 'aria-labelledby' => true, 'role' => true, 'xmlns' => true, 'width' => true, 'height' => true, 'viewbox' => true, ], 'g' => ['fill' => true], 'title' => ['title' => true], 'path' => [ 'd' => true, 'fill' => true, 'transform' => true, ], ]; $tags = array_merge($tags, $svg_args); } $tags = apply_filters('fluent_booking/allowed_html_tags', $tags); return wp_kses($html, $tags); } /** * Sanitize inputs recursively. * * @param array $input * @param array $sanitizeMap * * @return array $input */ public static function fcal_backend_sanitizer($inputs, $sanitizeMap = []) { $originalValues = $inputs; foreach ($inputs as $key => &$value) { if (is_array($value)) { $value = self::fcal_backend_sanitizer($value, $sanitizeMap); } else { $method = Arr::get($sanitizeMap, $key); if (!$method) { continue; } if (is_callable($method)) { $value = call_user_func($method, $value); } elseif (method_exists(self::class, $method)) { $value = call_user_func([self::class, $method], $value); } } } return apply_filters('fluent_booking/backend_sanitized_values', $inputs, $originalValues); } /** * Recursively implode a multi-dimentional array * * @param string $glue * @param array $array * * @return string */ public static function fcalImplodeRecursive($glue, array $array) { $fn = function ($glue, array $array) use (&$fn) { $result = ''; foreach ($array as $item) { if (is_array($item)) { $result .= $fn($glue, $item); } else { $result .= $glue . $item; } } return $result; }; return ltrim($fn($glue, $array), $glue); } public static function getEventColors() { return apply_filters('fluent_booking/event_colors', [ [ 'label' => __('Red-Orange', 'fluent-booking'), 'value' => '#ff4f00' ], [ 'label' => __('Deep Lilac', 'fluent-booking'), 'value' => '#e55cff' ], [ 'label' => __('Purple', 'fluent-booking'), 'value' => '#8247f5' ], [ 'label' => __('Vivid Blue', 'fluent-booking'), 'value' => '#0099ff' ], [ 'label' => __('Cyan', 'fluent-booking'), 'value' => '#0ae8f0' ], [ 'label' => __('Emerald Green', 'fluent-booking'), 'value' => '#17e885' ], [ 'label' => __('Lime Green', 'fluent-booking'), 'value' => '#ccf000' ], [ 'label' => __('Amber', 'fluent-booking'), 'value' => '#ffa600' ] ]); } public static function getMeetingDurations() { return apply_filters('fluent_booking/meeting_durations_schema', [ [ 'value' => '15', 'label' => __('15 Minutes', 'fluent-booking') ], [ 'value' => '30', 'label' => __('30 Minutes', 'fluent-booking') ], [ 'value' => '45', 'label' => __('45 Minutes', 'fluent-booking') ], [ 'value' => '60', 'label' => __('60 Minutes', 'fluent-booking') ], [ 'value' => 'custom', 'label' => __('Custom', 'fluent-booking') ] ]); } public static function getMeetingMultiDurations() { return apply_filters('fluent_booking/meeting_multi_durations_schema', [ [ 'value' => '5', 'label' => __('5 Minutes', 'fluent-booking') ], [ 'value' => '10', 'label' => __('10 Minutes', 'fluent-booking') ], [ 'value' => '15', 'label' => __('15 Minutes', 'fluent-booking') ], [ 'value' => '30', 'label' => __('30 Minutes', 'fluent-booking') ], [ 'value' => '45', 'label' => __('45 Minutes', 'fluent-booking') ], [ 'value' => '50', 'label' => __('50 Minutes', 'fluent-booking') ], [ 'value' => '60', 'label' => __('60 Minutes', 'fluent-booking') ], [ 'value' => '90', 'label' => __('90 Minutes', 'fluent-booking') ], [ 'value' => '120', 'label' => __('120 Minutes', 'fluent-booking') ], [ 'value' => '150', 'label' => __('150 Minutes', 'fluent-booking') ], [ 'value' => '180', 'label' => __('180 Minutes', 'fluent-booking') ], [ 'value' => '240', 'label' => __('240 Minutes', 'fluent-booking') ], [ 'value' => '480', 'label' => __('480 Minutes', 'fluent-booking') ] ]); } public static function getDurationLookup($multiDuration = false) { $durations = $multiDuration ? self::getMeetingMultiDurations() : self::getMeetingDurations(); $durationLookup = []; foreach ($durations as $duration) { $durationLookup[$duration['value']] = $duration['label']; } return $durationLookup; } public static function formatDuration($totalMinutes) { $days = floor($totalMinutes / 1440); // 1440 minutes in a day $hours = floor(($totalMinutes % 1440) / 60); $minutes = $totalMinutes % 60; $formattedDuration = []; if ($days > 0) { $unit = $days > 1 ? __('Days', 'fluent-booking') : __('Day', 'fluent-booking'); $formattedDuration[] = $days . ' ' . $unit; } if ($hours > 0) { $unit = $hours > 1 ? __('Hours', 'fluent-booking') : __('Hour', 'fluent-booking'); $formattedDuration[] = $hours . ' ' . $unit; } if ($minutes > 0 || empty($formattedDuration)) { $unit = $minutes > 1 ? __('Minutes', 'fluent-booking') : __('Minute', 'fluent-booking'); $formattedDuration[] = $minutes . ' ' . $unit; } return implode(' ', $formattedDuration); } public static function getBufferTimes() { return apply_filters('fluent_booking/buffer_times_schema', [ [ 'value' => '0', 'label' => __('No buffer time', 'fluent-booking') ], [ 'value' => '5', 'label' => __('5 Minutes', 'fluent-booking') ], [ 'value' => '10', 'label' => __('10 Minutes', 'fluent-booking') ], [ 'value' => '15', 'label' => __('15 Minutes', 'fluent-booking') ], [ 'value' => '20', 'label' => __('20 Minutes', 'fluent-booking') ], [ 'value' => '30', 'label' => __('30 Minutes', 'fluent-booking') ], [ 'value' => '45', 'label' => __('45 Minutes', 'fluent-booking') ], [ 'value' => '60', 'label' => __('60 Minutes', 'fluent-booking') ], [ 'value' => '90', 'label' => __('90 Minutes', 'fluent-booking') ], [ 'value' => '120', 'label' => __('120 Minutes', 'fluent-booking') ] ]); } public static function getSlotIntervals() { return apply_filters('fluent_booking/slot_intervals_schema', [ [ 'value' => '', 'label' => __('Use event length (default)', 'fluent-booking') ], [ 'value' => '5', 'label' => __('5 Minutes', 'fluent-booking') ], [ 'value' => '10', 'label' => __('10 Minutes', 'fluent-booking') ], [ 'value' => '15', 'label' => __('15 Minutes', 'fluent-booking') ], [ 'value' => '20', 'label' => __('20 Minutes', 'fluent-booking') ], [ 'value' => '30', 'label' => __('30 Minutes', 'fluent-booking') ], [ 'value' => '45', 'label' => __('45 Minutes', 'fluent-booking') ], [ 'value' => '60', 'label' => __('60 Minutes', 'fluent-booking') ], [ 'value' => '75', 'label' => __('75 Minutes', 'fluent-booking') ], [ 'value' => '90', 'label' => __('90 Minutes', 'fluent-booking') ], [ 'value' => '105', 'label' => __('105 Minutes', 'fluent-booking') ], [ 'value' => '120', 'label' => __('120 Minutes', 'fluent-booking') ] ]); } public static function getBookingStatusChangingTimes() { return apply_filters('fluent_booking/booking_status_changing_times_schema', [ [ 'value' => '5', 'label' => __('5 Minutes', 'fluent-booking') ], [ 'value' => '10', 'label' => __('10 Minutes', 'fluent-booking') ], [ 'value' => '20', 'label' => __('20 Minutes', 'fluent-booking') ], [ 'value' => '30', 'label' => __('30 Minutes', 'fluent-booking') ], [ 'value' => '40', 'label' => __('40 Minutes', 'fluent-booking') ], [ 'value' => '50', 'label' => __('50 Minutes', 'fluent-booking') ], [ 'value' => '60', 'label' => __('1 Hour', 'fluent-booking') ], [ 'value' => '120', 'label' => __('2 Hours', 'fluent-booking') ], [ 'value' => '180', 'label' => __('3 Hours', 'fluent-booking') ], [ 'value' => '360', 'label' => __('6 Hours', 'fluent-booking') ], [ 'value' => '720', 'label' => __('12 Hours', 'fluent-booking') ], [ 'value' => '1440', 'label' => __('1 Day', 'fluent-booking') ], [ 'value' => '2880', 'label' => __('2 Days', 'fluent-booking') ] ]); } public static function getBookingPeriodOptions() { return apply_filters('fluent_booking/booking_period_options', [ 'upcoming' => __('Upcoming', 'fluent-booking'), 'completed' => __('Completed', 'fluent-booking'), 'pending' => __('Pending', 'fluent-booking'), 'cancelled' => __('Cancelled', 'fluent-booking'), 'all' => __('All', 'fluent-booking'), ]); } public static function getWeekSelectTimes() { return apply_filters('fluent_booking/week_select_times_schema', [ 'start' => '00:00', 'step' => '00:15', 'end' => '23:45' ]); } public static function getOverrideSelectTimes() { return apply_filters('fluent_booking/override_select_times_schema', [ 'start' => '00:00', 'step' => '00:15', 'end' => '23:45' ]); } public static function getWeeklyScheduleSchema() { return apply_filters('fluent_booking/weekly_schedule_schema', [ 'sun' => [ 'enabled' => false, 'slots' => [] ], 'mon' => [ 'enabled' => true, 'slots' => [ ['start' => '09:00', 'end' => '17:00'] ], ], 'tue' => [ 'enabled' => true, 'slots' => [ ['start' => '09:00', 'end' => '17:00'] ], ], 'wed' => [ 'enabled' => true, 'slots' => [ ['start' => '09:00', 'end' => '17:00'] ], ], 'thu' => [ 'enabled' => true, 'slots' => [ ['start' => '09:00', 'end' => '17:00'] ], ], 'fri' => [ 'enabled' => true, 'slots' => [ ['start' => '09:00', 'end' => '17:00'] ], ], 'sat' => [ 'enabled' => false, 'slots' => [] ], ]); } public static function getCustomFieldTypes() { return apply_filters('fluent_booking/custom_fields_types', [ [ 'value' => 'email', 'label' => __('Email', 'fluent-booking') ], [ 'value' => 'text', 'label' => __('Text', 'fluent-booking') ], [ 'value' => 'textarea', 'label' => __('Textarea', 'fluent-booking') ], [ 'value' => 'number', 'label' => __('Number', 'fluent-booking') ], [ 'value' => 'phone', 'label' => __('Phone', 'fluent-booking') ], [ 'value' => 'radio', 'label' => __('Radio', 'fluent-booking') ], [ 'value' => 'dropdown', 'label' => __('Select', 'fluent-booking') ], [ 'value' => 'multi-select', 'label' => __('Multi Select', 'fluent-booking') ], [ 'value' => 'checkbox', 'label' => __('Checkbox', 'fluent-booking') ], [ 'value' => 'checkbox-group', 'label' => __('Checkbox Group', 'fluent-booking') ], [ 'value' => 'date', 'label' => __('Date', 'fluent-booking') ], [ 'value' => 'file', 'label' => __('File', 'fluent-booking') ], [ 'value' => 'hidden', 'label' => __('Hidden', 'fluent-booking') ], [ 'value' => 'terms-and-conditions', 'label' => __('Terms & Conditions', 'fluent-booking') ] ]); } public static function getDefaultTermsAndConditions() { $termsAndConditions = __('I have read and agree to the Terms and Conditions and Privacy Policy.', 'fluent-booking'); return apply_filters('fluent_booking/default_terms_and_conditions', $termsAndConditions); } public static function getDefaultEmailNotificationSettings() { $assetUrl = App::getInstance()['url.assets']; $checkImage = $assetUrl . 'images/check-mark.png'; $cancelImage = $assetUrl . 'images/cancel-mark.png'; $scheduleImage = $assetUrl . 'images/schedule-mark.png'; return apply_filters('fluent_booking/default_email_notification_settings', [ 'booking_conf_attendee' => [ 'enabled' => true, 'title' => __('Booking Confirmation Email to Attendee', 'fluent-booking'), 'email' => [ 'subject' => 'Booking Confirmation between {{host.name}} & {{guest.full_name}}', 'body' => '

Your event has been scheduled


Event Name

{{booking.event_name}} with {{host.name}}

When

{{booking.full_start_end_guest_timezone}}

Who

Where

{{booking.location_details_html}}

Additional notes

{{guest.note}}


' . __('Need to make a change?', 'fluent-booking') . ' ' . __('Reschedule', 'fluent-booking') . ' or ' . __('Cancel', 'fluent-booking') . '


' . self::getAddToCalendarHtml($assetUrl) ], ], 'booking_conf_host' => [ 'enabled' => true, 'is_host' => true, 'title' => __('Booking Confirmation Email to Organizer (You)', 'fluent-booking'), 'email' => [ 'additional_recipients' => '', 'subject' => 'New Booking: {{guest.full_name}} @ {{booking.start_date_time_for_host}}', 'body' => '

A new event has been scheduled


Event Name

{{booking.event_name}} with {{guest.full_name}}

When

{{booking.full_start_end_host_timezone}}

Who

Where

{{booking.location_details_html}}

Note

{{guest.note}}

Additional Data

{{guest.form_data_html}}


View on the Website

' ], ], 'reminder_to_attendee' => [ 'enabled' => false, 'title' => __('Configure Meeting Reminder to Attendee', 'fluent-booking'), 'email' => [ 'subject' => 'Meeting Reminder with {{host.name}} @ {{booking.start_date_time_for_attendee}}', 'body' => '

Reminder: Your meeting will start in {{booking.start_time_human_format}}


Event Name

{{booking.event_name}} with {{host.name}}

When

{{booking.full_start_end_guest_timezone}}

Who

Where

{{booking.location_details_html}}

Additional notes

{{guest.note}}


' . __('Need to make a change?', 'fluent-booking') . ' ' . __('Reschedule', 'fluent-booking') . ' or ' . __('Cancel', 'fluent-booking') . '

', 'times' => [ [ 'unit' => 'minutes', 'value' => 15, ] ] ], ], 'reminder_to_host' => [ 'enabled' => false, 'is_host' => true, 'title' => __('Configure Meeting Reminder to Organizer (You)', 'fluent-booking'), 'email' => [ 'additional_recipients' => '', 'subject' => 'Meeting Reminder with {{host.name}} @ {{booking.start_date_time_for_host}}', 'body' => '

Reminder: Your meeting will start in {{booking.start_time_human_format}}


Event Name

{{booking.event_name}} with {{guest.full_name}}

When

{{booking.full_start_end_host_timezone}}

Who

Where

{{booking.location_details_html}}

Note

{{guest.note}}

Additional Data

{{guest.form_data_html}}


View on the Website

', 'times' => [ [ 'unit' => 'minutes', 'value' => 15, ] ] ], ], 'cancelled_by_attendee' => [ 'enabled' => true, 'is_host' => true, 'title' => __('Booking Cancelled by Attendee (email to Organizer)', 'fluent-booking'), 'email' => [ 'additional_recipients' => '', 'subject' => 'A booking was cancelled with {{guest.full_name}}', 'body' => '

Booking Cancellation


A scheduled meeting has been canceled. Here are the details:

Event Name

{{booking.event_name}} with {{guest.full_name}}

When

{{booking.full_start_end_host_timezone}} (cancelled)

Cancellation Reason

{{booking.cancel_reason}}

Who

Where

{{booking.location_details_html}}

Note

{{guest.note}}

Additional Data

{{guest.form_data_html}}


View on the Website

' ], ], 'cancelled_by_host' => [ 'enabled' => true, 'title' => __('Booking Cancelled by Organizer (email to Attendee)', 'fluent-booking'), 'email' => [ 'subject' => 'Your booking was cancelled with {{host.name}}', 'body' => '

Booking Cancellation


Your scheduled meeting has been canceled. Here are the details:

Event Name

{{booking.event_name}} with {{guest.full_name}}

When

{{booking.full_start_end_host_timezone}} (cancelled)

Cancellation Reason

{{booking.cancel_reason}}

' ], ], 'rescheduled_by_attendee' => [ 'enabled' => true, 'is_host' => true, 'title' => __('Booking Rescheduled by Attendee (email to Organizer)', 'fluent-booking'), 'email' => [ 'additional_recipients' => '', 'subject' => 'A booking was rescheduled with {{guest.full_name}}', 'body' => '

Booking Rescheduled


A scheduled meeting has been rescheduled. Here are the details:

Event Name

{{booking.event_name}} with {{guest.full_name}}

When

New Time: {{booking.full_start_end_host_timezone}} (new)

Previous Time: {{booking.previous_meeting_time}}

Rescheduling Reason

{{booking.reschedule_reason}}

Who

Where

{{booking.location_details_html}}

Note

{{guest.note}}

Additional Data

{{guest.form_data_html}}


View on the Website

' ], ], 'rescheduled_by_host' => [ 'enabled' => true, 'title' => __('Booking Rescheduled by Organizer (email to Attendee)', 'fluent-booking'), 'email' => [ 'subject' => 'Your booking was rescheduled with {{host.name}}', 'body' => '

Booking Rescheduled


Your scheduled meeting has been rescheduled. Here are the details:

Event Name

{{booking.event_name}} with {{guest.full_name}}

When

New Time: {{booking.full_start_end_host_timezone}} (new)

Previous Time: {{booking.previous_meeting_time_guest_timezone}}

Rescheduling Reason

{{booking.reschedule_reason}}


' . __('Need to make a change?', 'fluent-booking') . ' ' . __('Reschedule', 'fluent-booking') . ' or ' . __('Cancel', 'fluent-booking') . '


' . self::getAddToCalendarHtml($assetUrl) ], ], 'booking_request_host' => [ 'enabled' => true, 'is_host' => true, 'title' => __('Booking Approval Request to Host (email to Organizer)', 'fluent-booking'), 'email' => [ 'additional_recipients' => '', 'subject' => 'Awaiting Approval: {{guest.full_name}} @ {{booking.start_date_time_for_host}}', 'body' => '

A booking is still waiting for your approval


Someone has requested to schedule an event on your calendar. Here are the details:

Event Name

{{booking.event_name}} with {{guest.full_name}}

When

{{booking.full_start_end_host_timezone}}

Who

Where

{{booking.location_details_html}}

Note

{{guest.note}}

Additional Data

{{guest.form_data_html}}


' . self::getConfirmAndRejectButton($assetUrl) . '

View on the Website

' ], ], 'booking_request_attendee' => [ 'enabled' => true, 'title' => __('Booking Submission Confirmation (email to Attendee)', 'fluent-booking'), 'email' => [ 'subject' => 'Booking Submitted: Meeting between {{host.name}} & {{guest.full_name}}', 'body' => '

Your booking has been submitted


Please wait for the host to confirm your booking.

Event Name

{{booking.event_name}} with {{host.name}}

When

{{booking.full_start_end_guest_timezone}}

Who

Where

{{booking.location_details_html}}

Additional notes

{{guest.note}}


' . __('Need to make a change?', 'fluent-booking') . ' ' . __('Reschedule', 'fluent-booking') . ' or ' . __('Cancel', 'fluent-booking') . '

' ], ], 'declined_by_host' => [ 'enabled' => true, 'title' => __('Booking Declined by Organizer (email to Attendee)', 'fluent-booking'), 'email' => [ 'subject' => 'Booking Declined: Your booking was declined with {{host.name}}', 'body' => '

Booking Declined


Your booking request has been declined. Here are the details:

Event Name

{{booking.event_name}} with {{guest.full_name}}

When

{{booking.full_start_end_host_timezone}} (declined)

Reason

{{booking.reject_reason}}

' ], ], ]); } public static function getAddToCalendarHtml($assetUrl = '') { $assetUrl = $assetUrl ?: App::getInstance()['url.assets']; $html = '
' . __('Add to calendar', 'fluent-booking') . 'Google CalendarOutlookMicrosoft OfficeOther Calendar
'; return apply_filters('fluent_booking/add_to_calendar_html', $html); } public static function getConfirmAndRejectButton($assetUrl = '') { $assetUrl = $assetUrl ?: App::getInstance()['url.assets']; $html = '

' . __('Confirm', 'fluent-booking') . '

 

' . __('Reject', 'fluent-booking') . '

'; return apply_filters('fluent_booking/confirm_and_reject_button_html', $html); } public static function getIframeHtml() { $html = ''; return apply_filters('fluent_booking/get_iframe_html', $html); } public static function getEditorShortCodes($calendarEvent = null, $isHtmlSupported = false, $iframeHtml = '') { if (!$isHtmlSupported) { $groups = [ 'guest' => [ 'title' => __('Attendee Data', 'fluent-booking'), 'key' => 'guest', 'shortcodes' => [ '{{guest.first_name}}' => __('Guest First Name', 'fluent-booking'), '{{guest.last_name}}' => __('Guest Last Name', 'fluent-booking'), '{{guest.full_name}}' => __('Guest Full Name', 'fluent-booking'), '{{guest.email}}' => __('Guest Email', 'fluent-booking'), '{{guest.note}}' => __('Guest Note', 'fluent-booking'), '{{booking.phone}}' => __('Guest Main Phone Number (if provided)', 'fluent-booking'), '{{guest.timezone}}' => __('Guest Timezone', 'fluent-booking'), '{{guest.total_guest}}' => __('Total Guest Count', 'fluent-booking') ] ], 'booking' => [ 'title' => __('Booking Data', 'fluent-booking'), 'key' => 'booking', 'shortcodes' => [ '{{booking.event_name}}' => __('Event Name', 'fluent-booking'), '{{booking.description}}' => __('Event Description', 'fluent-booking'), '{{booking.booking_title}}' => __('Booking Title', 'fluent-booking'), '{{booking.additional_guests}}' => __('Additional Guests', 'fluent-booking'), '{{booking.full_start_end_guest_timezone}}' => __('Full Start Date Time (with guest timezone)', 'fluent-booking'), '{{booking.full_start_end_host_timezone}}' => __('Full Start Date Time (with host timezone)', 'fluent-booking'), '{{booking.full_start_and_end_guest_timezone}}' => __('Full Start & End Date Time (with guest timezone)', 'fluent-booking'), '{{booking.full_start_and_end_host_timezone}}' => __('Full Start & End Date Time (with host timezone)', 'fluent-booking'), '{{booking.all_bookings_short_times_guest_timezone}}' => __('All Bookings Short Times (with guest timezone)', 'fluent-booking'), '{{booking.all_bookings_short_times_host_timezone}}' => __('All Bookings Short Times (with host timezone)', 'fluent-booking'), '{{booking.all_bookings_full_times_guest_timezone}}' => __('All Bookings Full Times (with guest timezone)', 'fluent-booking'), '{{booking.all_bookings_full_times_host_timezone}}' => __('All Bookings Full Times (with host timezone)', 'fluent-booking'), '{{booking.start_date_time}}' => __('Event Date Time (UTC)', 'fluent-booking'), '{{booking.start_date_time_for_attendee}}' => __('Event Date Time (with attendee timezone)', 'fluent-booking'), '{{booking.start_date_time_for_host}}' => __('Event Date Time (with host timezone)', 'fluent-booking'), '{{booking.start_date_time_for_attendee.format.Y-m-d}}' => __('Event Date Time (with attendee timezone) (Ex: 2024-05-20)', 'fluent-booking'), '{{booking.start_date_time_for_host.format.Y-m-d}}' => __('Event Date Time (with host timezone) (Ex: 2024-05-20)', 'fluent-booking'), '{{booking.location_details_text}}' => __('Event Location Details', 'fluent-booking'), '{{booking.cancel_reason}}' => __('Event Cancel Reason', 'fluent-booking'), '{{booking.start_time_human_format}}' => __('Event Start Time (ex: 2 hours from now)', 'fluent-booking'), '##booking.cancelation_url##' => __('Booking Cancellation URL', 'fluent-booking'), '##booking.reschedule_url##' => __('Booking Reschedule URL', 'fluent-booking'), '##booking.admin_booking_url##' => __('Booking Details Admin URL', 'fluent-booking'), '{{booking.source_url}}' => __('Source URL', 'fluent-booking'), '{{booking.utm_source}}' => __('UTM Source', 'fluent-booking'), '{{booking.utm_medium}}' => __('UTM Medium', 'fluent-booking'), '{{booking.utm_campaign}}' => __('UTM Campaign', 'fluent-booking'), '{{booking.utm_term}}' => __('UTM Term', 'fluent-booking'), '{{booking.utm_content}}' => __('UTM Content', 'fluent-booking'), '{{booking.booking_hash}}' => __('Unique Booking Hash', 'fluent-booking'), '{{booking.reschedule_reason}}' => __('Event Reschedule Reason', 'fluent-booking'), '{{booking.previous_meeting_date_time_host_timezone}}' => __('Previous Meeting Date & Time (with host timezone)', 'fluent-booking'), '{{booking.previous_meeting_date_time_guest_timezone}}' => __('Previous Meeting Date & Time (with guest timezone)', 'fluent-booking'), ] ], 'host' => [ 'title' => __('Host Data', 'fluent-booking'), 'key' => 'host', 'shortcodes' => [ '{{host.name}}' => __('Host Name', 'fluent-booking'), '{{host.email}}' => __('Host Email', 'fluent-booking'), '{{host.timezone}}' => __('Host Timezone', 'fluent-booking'), ] ], 'other' => [ 'title' => __('Other', 'fluent-booking'), 'key' => 'other', 'shortcodes' => [ '{{event.id}}' => __('Event ID', 'fluent-booking'), '{{calendar.id}}' => __('Calendar ID', 'fluent-booking'), '{{event.title}}' => __('Event Title', 'fluent-booking'), '{{calendar.title}}' => __('Calendar Title', 'fluent-booking'), '{{calendar.description}}' => __('Calendar Description', 'fluent-booking'), '{{add_booking_to_calendar}}' => __('Add Booking to Calendar', 'fluent-booking'), ] ] ]; } else { $groups = [ 'guest' => [ 'title' => __('Attendee Data', 'fluent-booking'), 'key' => 'guest', 'shortcodes' => [ '{{guest.first_name}}' => __('Guest First Name', 'fluent-booking'), '{{guest.last_name}}' => __('Guest Last Name', 'fluent-booking'), '{{guest.full_name}}' => __('Guest Full Name', 'fluent-booking'), '{{guest.email}}' => __('Guest Email', 'fluent-booking'), '{{booking.phone}}' => __('Guest Main Phone Number (if provided)', 'fluent-booking'), '{{guest.note}}' => __('Guest Note', 'fluent-booking'), '{{guest.timezone}}' => __('Guest Timezone', 'fluent-booking'), '{{guest.total_guest}}' => __('Total Guest Count', 'fluent-booking') ] ], 'booking' => [ 'title' => __('Booking Data', 'fluent-booking'), 'key' => 'booking', 'shortcodes' => [ '{{booking.event_name}}' => __('Event Name', 'fluent-booking'), '{{booking.description}}' => __('Event Description', 'fluent-booking'), '{{booking.booking_title}}' => __('Booking Title', 'fluent-booking'), '{{booking.additional_guests}}' => __('Additional Guests', 'fluent-booking'), '{{booking.full_start_end_guest_timezone}}' => __('Full Start Date Time (with guest timezone)', 'fluent-booking'), '{{booking.full_start_end_host_timezone}}' => __('Full Start Date Time (with host timezone)', 'fluent-booking'), '{{booking.full_start_and_end_guest_timezone}}' => __('Full Start & End Date Time (with guest timezone)', 'fluent-booking'), '{{booking.full_start_and_end_host_timezone}}' => __('Full Start & End Date Time (with host timezone)', 'fluent-booking'), '{{booking.all_bookings_short_times_guest_timezone}}' => __('All Bookings Short Times (with guest timezone)', 'fluent-booking'), '{{booking.all_bookings_short_times_host_timezone}}' => __('All Bookings Short Times (with host timezone)', 'fluent-booking'), '{{booking.all_bookings_full_times_guest_timezone}}' => __('All Bookings Full Times (with guest timezone)', 'fluent-booking'), '{{booking.all_bookings_full_times_host_timezone}}' => __('All Bookings Full Times (with host timezone)', 'fluent-booking'), '{{booking.start_date_time}}' => __('Event Date Time (UTC)', 'fluent-booking'), '{{booking.start_date_time_for_attendee}}' => __('Event Date Time (with guest timezone)', 'fluent-booking'), '{{booking.start_date_time_for_host}}' => __('Event Date Time (with host timezone)', 'fluent-booking'), '{{booking.start_date_time_for_attendee.format.Y-m-d}}' => __('Event Date Time (with attendee timezone) (Ex: 2024-05-20)', 'fluent-booking'), '{{booking.start_date_time_for_host.format.Y-m-d}}' => __('Event Date Time (with host timezone) (Ex: 2024-05-20)', 'fluent-booking'), '{{booking.location_details_html}}' => __('Event Location Details (HTML)', 'fluent-booking'), '{{booking.cancel_reason}}' => __('Event Cancel Reason', 'fluent-booking'), '{{booking.start_time_human_format}}' => __('Event Start Time (ex: 2 hours from now)', 'fluent-booking'), '##booking.cancelation_url##' => __('Booking Cancellation URL', 'fluent-booking'), '##booking.reschedule_url##' => __('Booking Reschedule URL', 'fluent-booking'), '##booking.admin_booking_url##' => __('Booking Details Admin URL', 'fluent-booking'), '{{booking.source_url}}' => __('Source URL', 'fluent-booking'), '{{booking.utm_source}}' => __('UTM Source', 'fluent-booking'), '{{booking.utm_medium}}' => __('UTM Medium', 'fluent-booking'), '{{booking.utm_campaign}}' => __('UTM Campaign', 'fluent-booking'), '{{booking.utm_term}}' => __('UTM Term', 'fluent-booking'), '{{booking.utm_content}}' => __('UTM Content', 'fluent-booking'), '{{booking.booking_hash}}' => __('Unique Booking Hash', 'fluent-booking'), '{{booking.reschedule_reason}}' => __('Event Reschedule Reason', 'fluent-booking'), '{{booking.previous_meeting_date_time_host_timezone}}' => __('Previous Meeting Date & Time (with host timezone)', 'fluent-booking'), '{{booking.previous_meeting_date_time_guest_timezone}}' => __('Previous Meeting Date & Time (with guest timezone)', 'fluent-booking'), ] ], 'host' => [ 'title' => __('Host Data', 'fluent-booking'), 'key' => 'host', 'shortcodes' => [ '{{host.name}}' => __('Host Name', 'fluent-booking'), '{{host.email}}' => __('Host Email', 'fluent-booking'), '{{host.timezone}}' => __('Host Timezone', 'fluent-booking'), ] ], 'other' => [ 'title' => __('Other', 'fluent-booking'), 'key' => 'other', 'shortcodes' => [ '{{event.id}}' => __('Event ID', 'fluent-booking'), '{{event.calendar_id}}' => __('Calendar ID', 'fluent-booking'), '{{event.title}}' => __('Event Title', 'fluent-booking'), '{{calendar.title}}' => __('Calendar Title', 'fluent-booking'), '{{calendar.description}}' => __('Calendar Description', 'fluent-booking'), '{{add_booking_to_calendar}}' => __('Add Booking to Calendar', 'fluent-booking'), ] ] ]; } if ($calendarEvent) { $customFields = BookingFieldService::getCustomFields($calendarEvent, true); foreach ($customFields as $fieldKey => $field) { $groups['booking']['shortcodes']['{{booking.custom.' . $fieldKey . '}}'] = $field['label']; if ($field['type'] == 'date') { $groups['booking']['shortcodes']['{{booking.custom.' . $fieldKey . '.format.Y-m-d}}'] = $field['label'] . ' (Ex: 2024-05-20)'; } } if (Helper::isPaymentEnabled($calendarEvent)) { $groups['payment'] = [ 'title' => __('Payment Data', 'fluent-booking'), 'key' => 'payment', 'shortcodes' => [ '{{payment.payment_total}}' => __('Payment Total', 'fluent-booking'), '{{payment.payment_status}}' => __('Payment Status', 'fluent-booking'), '{{payment.payment_method}}' => __('Payment Method', 'fluent-booking'), '{{payment.currency}}' => __('Currency', 'fluent-booking'), '{{payment.payment_date}}' => __('Payment Date', 'fluent-booking'), ] ]; if ($isHtmlSupported) { $groups['payment']['shortcodes']['{{payment.receipt_html}}'] = __('Payment Receipt (HTML)', 'fluent-booking'); } } if ($calendarEvent->isMultiHostsEvent()) { $totalhost = count($calendarEvent->getHostIds()); for ($i = 1; $i < $totalhost; $i++) { $groups['host']['shortcodes']['{{team_member.' . $i . '.name}}'] = __('Team Member ', 'fluent-booking') . $i . __(' Name', 'fluent-booking'); $groups['host']['shortcodes']['{{team_member.' . $i . '.email}}'] = __('Team Member ', 'fluent-booking') . $i . __(' Email', 'fluent-booking'); } } } return apply_filters('fluent_booking/editor_shortcodes_groups', $groups, $calendarEvent, $isHtmlSupported); } public static function encryptKey($value) { if (!$value) { return $value; } if (!extension_loaded('openssl')) { return $value; } $salt = (defined('LOGGED_IN_SALT') && '' !== LOGGED_IN_SALT) ? LOGGED_IN_SALT : 'this-is-a-fallback-salt-but-not-secure'; if (defined('FLUENT_BOOKING_ENCRYPTION_KEY')) { $key = FLUENT_BOOKING_ENCRYPTION_KEY; } else { $key = (defined('LOGGED_IN_KEY') && '' !== LOGGED_IN_KEY) ? LOGGED_IN_KEY : 'this-is-a-fallback-key-but-not-secure'; } $method = 'aes-256-ctr'; $ivlen = openssl_cipher_iv_length($method); $iv = openssl_random_pseudo_bytes($ivlen); $raw_value = openssl_encrypt($value . $salt, $method, $key, 0, $iv); if (!$raw_value) { return false; } return base64_encode($iv . $raw_value); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode } public static function decryptKey($raw_value) { if (!$raw_value) { return $raw_value; } if (!extension_loaded('openssl')) { return $raw_value; } $raw_value = base64_decode($raw_value, true); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode if ($raw_value === false) { return false; } $method = 'aes-256-ctr'; $ivlen = openssl_cipher_iv_length($method); if ($ivlen === false || strlen($raw_value) <= $ivlen) { return false; } $iv = substr($raw_value, 0, $ivlen); $raw_value = substr($raw_value, $ivlen); if (defined('FLUENT_BOOKING_ENCRYPTION_KEY')) { $key = FLUENT_BOOKING_ENCRYPTION_KEY; } else { $key = (defined('LOGGED_IN_KEY') && '' !== LOGGED_IN_KEY) ? LOGGED_IN_KEY : 'this-is-a-fallback-key-but-not-secure'; } $salt = (defined('LOGGED_IN_SALT') && '' !== LOGGED_IN_SALT) ? LOGGED_IN_SALT : 'this-is-a-fallback-salt-but-not-secure'; $value = openssl_decrypt($raw_value, $method, $key, 0, $iv); if (!is_string($value) || substr($value, -strlen($salt)) !== $salt) { return false; } return substr($value, 0, -strlen($salt)); } public static function debugLog($data) { if (defined('FLUENT_BOOKING_DEBUG') && FLUENT_BOOKING_DEBUG) { error_log(print_r($data, true)); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log,WordPress.PHP.DevelopmentFunctions.error_log_print_r } } public static function getGlobalSettings($settingsKey = null) { $defaults = [ 'payments' => [ 'currency' => 'USD', 'is_active' => 'no' ], 'emailing' => [ 'from_name' => '', 'from_email' => '', 'reply_to_name' => '', 'reply_to_email' => '', 'use_host_name' => '', 'use_host_email_on_reply' => '', 'attach_ics_on_confirmation' => '', 'email_footer' => '' ], 'administration' => [ 'admin_email' => '{{wp.admin_email}}', 'summary_notification' => 'no', 'notification_frequency' => 'daily', 'notification_day' => 'mon', 'start_day' => 'sun', 'auto_cancel_timing' => '10', 'auto_complete_timing' => '60', 'default_country' => '' ], 'time_format' => '12', 'theme' => 'system-default' ]; $settings = get_option('_fluent_booking_settings', []); if (empty($settings)) { $settings = []; } $settings['payments'] = CurrenciesHelper::getGlobalCurrencySettings(); $settings = wp_parse_args($settings, $defaults); $emailSettings = $settings['emailing']; if (empty($settings['administration']['auto_cancel_timing'])) { $settings['administration']['auto_cancel_timing'] = '10'; } if (empty($settings['administration']['auto_complete_timing'])) { $settings['administration']['auto_complete_timing'] = '10'; } if (empty($emailSettings['from_name']) && defined('FLUENTCRM')) { $crmSettings = fluentcrmGetGlobalSettings('email_settings', []); $emailSettings['from_name'] = Arr::get($crmSettings, 'from_name'); if (empty($emailSettings['from_email'])) { $emailSettings['from_email'] = Arr::get($crmSettings, 'from_email'); } if (empty($emailSettings['reply_to_name'])) { $emailSettings['reply_to_name'] = Arr::get($crmSettings, 'reply_to_name'); } if (empty($emailSettings['reply_to_email'])) { $emailSettings['reply_to_email'] = Arr::get($crmSettings, 'reply_to_email'); } $settings['emailing'] = $emailSettings; } if ($settingsKey) { return Arr::get($settings, $settingsKey, []); } return $settings; } public static function getGlobalAdminSetting($key = null, $default = null) { static $settings; if ($settings) { if ($key) { return Arr::get($settings, $key, $default); } return $settings; } $globalSettings = self::getGlobalSettings(); $settings = Arr::get($globalSettings, 'administration', []); if ($key) { return Arr::get($settings, $key, $default); } return $settings; } public static function getDefaultTimeFormat() { static $format; if ($format) { return $format; } $settings = self::getGlobalSettings(); $format = Arr::get($settings, 'time_format', '24'); return $format; } public static function getDefaultBookingFilters() { return apply_filters('fluent_booking/default_booking_filters', [ 'period' => 'upcoming', 'author' => 'me', // me, all, calendar_id 'event' => 'all', 'event_type' => 'all' ]); } public static function getDefaultPaginations() { return apply_filters('fluent_booking/default_paginations', [ 'bookings' => 10, 'calendars' => 10, 'coupons' => 10, 'availabilities' => 10 ]); } public static function getVerifiedSenders() { $verifiedSenders = []; if (defined('FLUENTMAIL')) { $smtpSettings = get_option('fluentmail-settings', []); if ($smtpSettings && count($smtpSettings['mappings'])) { $verifiedSenders = array_keys($smtpSettings['mappings']); } } /** * Filter the verified email senders * @param array $verifiedSenders */ return apply_filters('fluent_booking/verfied_email_senders', $verifiedSenders); } public static function getBookingReceiptLandingBaseUrl() { return apply_filters('fluent_booking/booking_receipt_landing_base_url', site_url('/')); } public static function getGlobalModuleSettings($cached = true) { static $settings = null; if ($cached && $settings !== null) { return $settings; } $settings = get_option('_fluent_booking_enabled_modules', []); if (empty($settings) || !\is_array($settings)) { $settings = []; } return $settings; } public static function updateGlobalModuleSettings($settings = []) { if (!is_array($settings)) { $settings = []; } update_option('_fluent_booking_enabled_modules', $settings); return self::getGlobalModuleSettings(false); } public static function isModuleEnabled($module) { $settings = self::getGlobalModuleSettings(); return isset($settings[$module]) && $settings[$module] === 'yes'; } /** * @return mixed|void */ public static function fluentbooking_is_rtl() { /** * If FluentBooking is running on RTL Mode * * @param bool $is_rtl */ return apply_filters('fluent_booking/is_rtl', is_rtl()); } public static function fluentBookingUserAvatar($id_or_email, $args) { if (empty($id_or_email)) { return ''; } return apply_filters('fluent_booking/author_photo', get_avatar_url($id_or_email), $args); } public static function getPrefSettings($cached = true) { static $pref = null; if ($cached && $pref) { return $pref; } $settings = [ 'frontend' => [ 'enabled' => 'no', 'slug' => 'my-bookings', 'render_type' => 'standalone', 'page_id' => '' ], 'coupon' => [ 'enabled' => 'no' ] ]; $storedSettings = get_option('fluent_booking_modules', []); if ($storedSettings && is_array($storedSettings)) { $settings = wp_parse_args($storedSettings, $settings); } else { update_option('fluent_booking_modules', $settings, 'yes'); } $pref = $settings; return $settings; } public static function getPrefSettins($cached = true) { return self::getPrefSettings($cached); } public static function getFeatures() { return apply_filters('fluent_booking/get_features', [ 'has_fluentcrm' => defined('FLUENTCRM'), 'has_fluentsmtp' => defined('FLUENTMAIL'), 'has_fluentform' => defined('FLUENTFORM'), 'has_fluentboards' => defined('FLUENT_BOARDS'), 'has_fluentcart' => defined('FLUENTCART_VERSION') ]); } public static function getActiveThemeName() { $ins = get_option('_fb_ins_by'); if ($ins) { return sanitize_text_field($ins); } return get_option('template'); } /** * Per-IP fixed-window rate limiter for public AJAX/REST endpoints. * * @param string $action Action name (e.g. apply_coupon, schedule_meeting). * @param int $limit Max requests per window. * @param int $window Window in seconds. * @return bool True if under the limit (and the count was incremented), * false if over. */ public static function checkRateLimit($action, $limit, $window = 60) { $args = apply_filters('fluent_booking/public_ajax_ratelimit', [ 'limit' => $limit, 'window' => $window, ], $action); $limit = max(1, (int) (isset($args['limit']) ? $args['limit'] : $limit)); $window = max(1, (int) (isset($args['window']) ? $args['window'] : $window)); $key = 'fcal_ratelimit_' . $action . '_' . md5(self::getIp()); $count = (int) get_transient($key); if ($count >= $limit) { return false; } set_transient($key, $count + 1, $window); return true; } /** * Run a callback inside a database transaction, re-throwing on failure so * the caller decides how to report it. * * \Throwable, not \Exception: a TypeError is an Error, and an * Exception-only catch would leave the transaction open. * * Database writes only — a hook fired in here would hold the callback's * rows locked for the length of a listener's outbound request. * * @param callable $callback * * @return mixed * * @throws \Throwable after the rollback */ public static function dbTransaction($callback) { $db = App::getInstance('db'); $db->beginTransaction(); try { $result = $callback(); $db->commit(); return $result; } catch (\Throwable $e) { $db->rollBack(); throw $e; } } }