enqueueAssets(); }, 100); add_action('admin_head', function () { if (!isset($_REQUEST['page']) || $_REQUEST['page'] != 'fluent-booking') { // phpcs:ignore WordPress.Security.NonceVerification.Recommended return; } $this->printThemeClass(); }, 1); } /** * Put the dark class on the document before the page paints. * * The theme is chosen in the browser, so the server cannot know it - which means * without this the first frame is always light, and somebody on dark gets a white flash * on every page load. This runs in
, before wp-admin's own markup, and is the * only reason it is inline rather than in global_admin.js: a file in the footer is too * late to matter, and one in the header is still a request the paint would wait on. * * `fluent_theme_mode` is the key every Fluent plugin shares (see global_admin.js), and it * holds a bare `light`, `dark` or `system`, so `system` is resolved here against the * machine's own preference. does not exist yet, so only is marked; * global_admin.js puts the same classes on and #wpbody-content once it runs. * * @return void */ public function printThemeClass() { $script = <<<'JS' (function(){try{var s=localStorage.getItem('fluent_theme_mode')||''; if(!s){s=localStorage.getItem('fcal_color_mode')==='dark'?'dark':'light';} if(s==='system'){s='system:'+(window.matchMedia&&window.matchMedia('(prefers-color-scheme: dark)').matches?'dark':'light');} if(s==='dark'||s==='system:dark'){var e=document.documentElement;e.classList.add('fluent_theme_dark');e.classList.add('dark');}}catch(e){}})(); JS; echo ''; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped } public function add() { $capability = PermissionManager::getMenuPermission(); $menuPriority = 26; if (defined('FLUENTCRM')) { $menuPriority = 4; } add_menu_page( __('Fluent Booking', 'fluent-booking'), __('Fluent Booking', 'fluent-booking'), $capability, 'fluent-booking', [$this, 'render'], $this->getMenuIcon(), $menuPriority ); add_submenu_page( 'fluent-booking', __('Dashboard', 'fluent-booking'), __('Dashboard', 'fluent-booking'), $capability, 'fluent-booking', [$this, 'render'] ); add_submenu_page( 'fluent-booking', __('Calendars', 'fluent-booking'), __('Calendars', 'fluent-booking'), $capability, 'fluent-booking#/calendars', [$this, 'render'] ); add_submenu_page( 'fluent-booking', __('Bookings', 'fluent-booking'), __('Bookings', 'fluent-booking'), $capability, 'fluent-booking#/scheduled-events', [$this, 'render'] ); add_submenu_page( 'fluent-booking', __('Availability', 'fluent-booking'), __('Availability', 'fluent-booking'), $capability, 'fluent-booking#/availability', [$this, 'render'] ); add_submenu_page( 'fluent-booking', __('Settings', 'fluent-booking'), __('Settings', 'fluent-booking'), $capability, 'fluent-booking#/settings/general-settings', [$this, 'render'] ); } public function render() { if (!as_has_scheduled_action('fluent_booking_five_minutes_tasks')) { as_schedule_recurring_action(time(), (60 * 5), 'fluent_booking_five_minutes_tasks', [], 'fluent-booking', true); } // The daily task carries the summary report and the MCP record purge. // A site that never got it — a secondary blog on a network activation, // or one that lost it — would grow fcal_mcp_g_* rows forever. if (!as_has_scheduled_action('fluent_booking/daily_tasks')) { as_schedule_recurring_action(time(), (60 * 60 * 24), 'fluent_booking/daily_tasks', [], 'fluent-booking', true); } $this->changeFooter(); $app = App::getInstance(); $config = $app->config; $name = $config->get('app.name'); $slug = $config->get('app.slug'); $baseUrl = Helper::getAppBaseUrl(); if (is_admin()) { $baseUrl = admin_url('admin.php?page=fluent-booking#/'); } $isNew = $this->isNew(); $menuItems = [ [ 'key' => 'dashboard', 'label' => $isNew ? __('Getting Started', 'fluent-booking') : __('Dashboard', 'fluent-booking'), 'permalink' => $baseUrl ], [ 'key' => 'calendars', 'label' => __('Calendars', 'fluent-booking'), 'permalink' => $baseUrl . 'calendars' ], [ 'key' => 'scheduled-events', 'label' => __('Bookings', 'fluent-booking'), 'permalink' => $baseUrl . 'scheduled-events', ], [ 'key' => 'availability', 'label' => __('Availability', 'fluent-booking'), 'permalink' => $baseUrl . 'availability' ] ]; $settingItems = []; if (PermissionManager::userCan('manage_all_data')) { $settingItems = [ 'key' => 'settings', 'label' => __('Settings', 'fluent-booking'), 'permalink' => $baseUrl . 'settings/general-settings' ]; } $menuItems = apply_filters('fluent_booking/admin_menu_items', $menuItems); $assets = $app['url.assets']; $portalVars = apply_filters('fluent_booking/admin_portal_vars', [ 'name' => $name, 'slug' => $slug, 'menuItems' => $menuItems, 'settings' => $settingItems, 'baseUrl' => $baseUrl, 'logo' => $assets . 'images/logo.svg', 'dark_logo' => $assets . 'images/logo_dark.svg', ]); do_action('fluent_booking/admin_app_rendering'); $app->view->render('admin.menu', $portalVars); } public function changeFooter() { add_filter('admin_footer_text', function ($content) { $url = 'https://fluentbooking.com/'; /* translators: %s: URL of the FluentBooking website */ return sprintf(wp_kses(__('Thank you for using FluentBooking.', 'fluent-booking'), array('a' => array('href' => array()))), esc_url($url)) . ''; }); add_filter('update_footer', function ($text) { return FLUENT_BOOKING_VERSION; }); } public function enqueueAssets() { $app = App::getInstance(); $isRtl = Helper::fluentbooking_is_rtl(); $slug = $app->config->get('app.slug'); $assetsVersion = Vite::isDev() ? time() : FLUENT_BOOKING_ASSETS_VERSION; if ($isRtl) { Vite::enqueueStyle('fluentbooking_admin_rtl', 'admin_rtl_css', [], $assetsVersion); } add_action('wp_print_scripts', function () { $isSkip = apply_filters('fluent_booking/skip_no_conflict', false, 'scripts'); if ($isSkip) { return; } global $wp_scripts; if (!$wp_scripts) { return; } $approvedSlugs = apply_filters('fluent_booking/asset_listed_slugs', [ '\/fluent-crm\/' ]); $approvedSlugs[] = '\/fluent-booking\/'; $approvedSlugs[] = '\/fluent-booking-pro\/'; $approvedSlugs = array_unique($approvedSlugs); $approvedSlugs = implode('|', $approvedSlugs); $pluginUrl = plugins_url(); $pluginUrl = str_replace(['http:', 'https:'], '', $pluginUrl); foreach ($wp_scripts->queue as $script) { if (empty($wp_scripts->registered[$script]) || empty($wp_scripts->registered[$script]->src)) { continue; } $src = $wp_scripts->registered[$script]->src; $isMatched = (strpos($src, $pluginUrl) !== false) && !preg_match('/' . $approvedSlugs . '/', $src); if (!$isMatched) { continue; } wp_dequeue_script($wp_scripts->registered[$script]->handle); } }); add_action('wp_print_styles', function () { $isSkip = apply_filters('fluent_booking/skip_no_conflict', false, 'styles'); if ($isSkip) { return; } global $wp_styles; if (!$wp_styles) { return; } $approvedSlugs = apply_filters('fluent_booking/asset_listed_slugs', [ '\/fluent-crm\/' ]); $approvedSlugs[] = '\/fluent-booking\/'; $approvedSlugs[] = '\/fluent-booking-pro\/'; $approvedSlugs = array_unique($approvedSlugs); $approvedSlugs = implode('|', $approvedSlugs); $pluginUrl = plugins_url(); $themeUrl = get_theme_root_uri(); $pluginUrl = str_replace(['http:', 'https:'], '', $pluginUrl); $themeUrl = str_replace(['http:', 'https:'], '', $themeUrl); foreach ($wp_styles->queue as $script) { if (empty($wp_styles->registered[$script]) || empty($wp_styles->registered[$script]->src)) { continue; } $src = $wp_styles->registered[$script]->src; $pluginMatched = (strpos($src, $pluginUrl) !== false) && !preg_match('/' . $approvedSlugs . '/', $src); $themeMatched = (strpos($src, $themeUrl) !== false) && !preg_match('/' . $approvedSlugs . '/', $src); if (!$pluginMatched && !$themeMatched) { continue; } wp_dequeue_style($wp_styles->registered[$script]->handle); } }, 999999); Vite::enqueueStyle('fluent_booing_admin_app', 'admin_css', [], $assetsVersion, 'all'); do_action($slug . '_loading_app'); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound Vite::enqueueScript($slug . '_admin_app', 'admin_app', array('jquery'), $assetsVersion); Vite::enqueueScript($slug . '_global_admin', 'global_admin', array(), $assetsVersion); if (function_exists('wp_enqueue_editor')) { add_filter('user_can_richedit', '__return_true'); wp_enqueue_editor(); wp_enqueue_media(); } wp_localize_script($slug . '_admin_app', 'fluentFrameworkAdmin', $this->getDashboardVars($app)); } public function getDashboardVars($app) { $assets = $app['url.assets']; $currentUser = get_user_by('ID', get_current_user_id()); $currentUsername = trim($currentUser->first_name . ' ' . $currentUser->last_name); if (!$currentUsername) { $currentUsername = $currentUser->display_name; } $isNew = $this->isNew(); $requireSlug = false; if ($isNew) { $result = $this->maybeAutoCreateCalendar($currentUser); if (!$result) { $requireSlug = true; } } $calendarId = null; $firstCalendar = Calendar::where('user_id', $currentUser->ID)->where('type', 'simple')->first(); if ($firstCalendar) { $calendarId = $firstCalendar->id; } $hasAllAccess = false; if (PermissionManager::hasAllCalendarAccess()) { $hasAllAccess = true; } $eventColors = Helper::getEventColors(); $meetingDurations = Helper::getMeetingDurations(); $multiDurations = Helper::getMeetingMultiDurations(); $durationLookup = Helper::getDurationLookup(); $multiDurationLookup = Helper::getDurationLookup(true); $scheduleSchema = Helper::getWeeklyScheduleSchema(); $bufferTimes = Helper::getBufferTimes(); $slotIntervals = Helper::getSlotIntervals(); $customFieldTypes = Helper::getCustomFieldTypes(); $weekSelectTimes = Helper::getWeekSelectTimes(); $overrideSelectTimes = Helper::getOverrideSelectTimes(); $statusChangingTimes = Helper::getBookingStatusChangingTimes(); $defaultTermsAndConditions = Helper::getDefaultTermsAndConditions(); $locationFields = (new CalendarSlot())->getLocationFields(); return apply_filters('fluent_booking/admin_vars', [ 'slug' => $slug = $app->config->get('app.slug'), 'nonce' => wp_create_nonce($slug), 'rest' => $this->getRestInfo($app), 'brand_logo' => $this->getMenuIcon(), 'asset_url' => $assets, 'event_colors' => $eventColors, 'meeting_durations' => $meetingDurations, 'multi_durations' => $multiDurations, 'buffer_times' => $bufferTimes, 'slot_intervals' => $slotIntervals, 'schedule_schema' => $scheduleSchema, 'location_fields' => $locationFields, 'custom_field_types' => $customFieldTypes, 'week_select_times' => $weekSelectTimes, 'duration_lookup' => $durationLookup, 'multi_duration_lookup' => $multiDurationLookup, 'override_select_times' => $overrideSelectTimes, 'status_changing_times' => $statusChangingTimes, 'default_terms' => $defaultTermsAndConditions, 'me' => [ 'id' => $currentUser->ID, 'calendar_id' => $calendarId, 'full_name' => $currentUsername, 'avatar' => get_avatar_url($currentUser->ID, ['size' => 96]), 'email' => $currentUser->user_email, 'is_admin' => $hasAllAccess, 'permissions' => PermissionManager::getUserPermissions($currentUser, false), ], 'all_hosts' => Calendar::getAllHosts(), 'is_new' => $isNew, 'require_slug' => $requireSlug, 'site_url' => site_url('/'), 'site_title' => get_bloginfo('name'), 'upgrade_url' => Helper::getUpgradeUrl(), 'timezones' => DateTimeHelper::getTimeZones(true), 'features' => Helper::getFeatures(), 'supported_features' => apply_filters('fluent_booking/supported_featured', [ 'multi_users' => true ]), 'i18' => [ 'date_time_config' => DateTimeHelper::getI18nDateTimeConfig(), ], 'has_pro' => defined('FLUENT_BOOKING_PRO_DIR_FILE'), 'require_upgrade' => defined('FLUENT_BOOKING_PRO_DIR_FILE') && !defined('FLUENT_BOOKING_LITE'), 'dashboard_notices' => apply_filters('fluent_booking/dashboard_notices', []), 'payment_methods' => apply_filters('fluent_booking/payment/get_all_methods', []), 'trans' => TransStrings::getStrings(), 'date_format' => DateTimeHelper::getDateFormatter(true), 'time_format' => DateTimeHelper::getTimeFormatter(true), 'date_time_formatter' => DateTimeHelper::getDateFormatter(true) . ', ' . DateTimeHelper::getTimeFormatter(true), 'available_date_formats' => DateTimeHelper::getAvailableDateFormats(), 'default_booking_filters' => Helper::getDefaultBookingFilters(), 'default_paginations' => Helper::getDefaultPaginations(), 'pref_settings' => Helper::getPrefSettings(), 'settings_menu_items' => static::settingsMenuItems(), 'admin_url' => admin_url(), 'is_rtl' => Helper::fluentbooking_is_rtl(), 'iframe_html' => Helper::getIframeHtml() ]); } protected function getRestInfo($app) { $ns = $app->config->get('app.rest_namespace'); $ver = $app->config->get('app.rest_version'); return [ 'base_url' => esc_url_raw(rest_url()), 'url' => rest_url($ns . '/' . $ver), 'nonce' => wp_create_nonce('wp_rest'), 'namespace' => $ns, 'version' => $ver ]; } protected function getMenuIcon() { return 'data:image/svg+xml;base64,' . base64_encode(''); } protected function isNew() { return apply_filters('fluent_booking/is_new', !Calendar::first()); } /** * @param $user \WP_User * @return bool | Calendar */ protected function maybeAutoCreateCalendar($user) { if (!apply_filters('fluent_booking/auto_create_calendar', false, $user)) { return false; } $userName = $user->user_login; if (is_email($userName)) { $userName = explode('@', $userName); $userName = $userName[0]; } if (!Helper::isCalendarSlugAvailable($userName, true)) { return false; } $personName = trim($user->first_name . ' ' . $user->last_name); if (!$personName) { $personName = $user->display_name; } $data = [ 'user_id' => $user->ID, 'title' => $personName, 'slug' => $userName ]; return Calendar::create($data); } public static function settingsMenuItems() { $app = App::getInstance(); $urlAssets = $app['url.assets']; return apply_filters('fluent_booking/settings_menu_items', [ 'general_settings' => [ 'title' => __('General', 'fluent-booking'), 'disable' => false, 'el_icon' => 'Operation', 'component_type' => 'StandAloneComponent', 'class' => 'general_settings', 'route' => [ 'name' => 'general_settings' ] ], 'team_members' => [ 'title' => __('Team', 'fluent-booking'), 'disable' => true, 'el_icon' => 'TeamIcon', 'component_type' => 'StandAloneComponent', 'class' => 'team_members', 'route' => [ 'name' => 'team_members' ] ], 'payment_settings' => [ 'title' => __('Payment', 'fluent-booking'), 'disable' => true, 'el_icon' => 'Money', 'component_type' => 'PaymentSettingsComponent', 'class' => 'payment_settings', 'route' => [ 'name' => 'global_payment_settings', ], 'children' => [ 'payment_settings' => [ 'title' => __('Settings', 'fluent-booking'), 'disable' => true, 'route' => [ 'name' => 'global_payment_settings' ] ], 'payment_methods' => [ 'title' => __('Payment Methods', 'fluent-booking'), 'disable' => true, 'route' => [ 'name' => 'payment_methods', 'params' => [ 'settings_key' => 'stripe' ] ] ], 'payment_coupons' => [ 'title' => __('Coupons', 'fluent-booking'), 'disable' => true, 'route' => [ 'name' => 'payment_coupons' ] ] ], ], 'google' => [ 'title' => __('Google Calendar / Meet', 'fluent-booking'), 'disable' => true, 'icon_url' => $urlAssets . 'images/gg-calendar.svg', 'component_type' => 'StandAloneComponent', 'class' => 'configure_google_calendar', 'route' => [ 'name' => 'configure-google' ] ], 'outlook' => [ 'title' => __('Outlook Calendar / MS Teams', 'fluent-booking'), 'disable' => true, 'icon_url' => $urlAssets . 'images/ol-icon-color.svg', 'component_type' => 'GlobalSettingsComponent', 'class' => 'configure_outlook_calendar', 'route' => [ 'name' => 'configure-integrations', 'params' => [ 'settings_key' => 'outlook' ] ] ], 'apple_calendar' => [ 'title' => __('Apple Calendar', 'fluent-booking'), 'disable' => true, 'icon_url' => $urlAssets . 'images/a-cal.svg', 'component_type' => 'GlobalSettingsComponent', 'class' => 'configure_apple_calendar', 'route' => [ 'name' => 'configure-integrations', 'params' => [ 'settings_key' => 'apple_calendar' ] ] ], 'next_cloud_calendar' => [ 'title' => __('Nextcloud Calendar', 'fluent-booking'), 'disable' => true, 'svg_icon' => '', 'component_type' => 'GlobalSettingsComponent', 'class' => 'configure_nextcloud_calendar', 'route' => [ 'name' => 'configure-integrations', 'params' => [ 'settings_key' => 'next_cloud_calendar' ] ] ], 'zoom_meeting' => [ 'title' => __('Zoom', 'fluent-booking'), 'disable' => true, 'icon_url' => $urlAssets . 'images/zoom.svg', 'component_type' => 'StandAloneComponent', 'class' => 'zoom_integrations', 'route' => [ 'name' => 'zoom_integrations' ] ], 'twilio' => [ 'title' => __('SMS by Twilio', 'fluent-booking'), 'disable' => true, 'svg_icon' => '', 'component_type' => 'GlobalSettingsComponent', 'class' => 'configure_twilio', 'route' => [ 'name' => 'configure-integrations', 'params' => [ 'settings_key' => 'twilio' ] ] ], 'license' => [ 'title' => __('License', 'fluent-booking'), 'disable' => true, 'el_icon' => 'Lock', 'component_type' => 'StandAloneComponent', 'class' => 'configure_license', 'route' => [ 'name' => 'license' ] ] ]); } public static function getEventSettingsMenuItems($event) { return apply_filters('fluent_booking/calendar_event_setting_menu_items', [ 'event_details' => [ 'type' => 'route', 'visible' => true, 'disable' => false, 'route' => [ 'name' => 'event_details', 'params' => [ 'calendar_id' => $event->calendar_id, 'event_id' => $event->id ] ], 'label' => __('Event Details', 'fluent-booking'), 'svgIcon' => '' ], 'assignment' => [ 'type' => 'route', 'visible' => false, 'disable' => true, 'route' => [ 'name' => 'assignment', 'params' => [ 'calendar_id' => $event->calendar_id, 'event_id' => $event->id ] ], 'label' => __('Assignment', 'fluent-booking'), 'svgIcon' => '' ], 'availability_settings' => [ 'type' => 'route', 'visible' => true, 'disable' => false, 'route' => [ 'name' => 'availability_settings', 'params' => [ 'calendar_id' => $event->calendar_id, 'event_id' => $event->id ] ], 'label' => __('Availability', 'fluent-booking'), 'svgIcon' => '' ], 'limit_settings' => [ 'type' => 'route', 'visible' => true, 'disable' => false, 'route' => [ 'name' => 'limit_settings', 'params' => [ 'calendar_id' => $event->calendar_id, 'event_id' => $event->id ] ], 'label' => __('Limits', 'fluent-booking'), 'elIcon' => 'Clock' ], 'question_settings' => [ 'type' => 'route', 'visible' => true, 'disable' => false, 'route' => [ 'name' => 'question_settings', 'params' => [ 'calendar_id' => $event->calendar_id, 'event_id' => $event->id ] ], 'label' => __('Question Settings', 'fluent-booking'), 'svgIcon' => '' ], 'email_notification' => [ 'type' => 'route', 'visible' => true, 'disable' => false, 'route' => [ 'name' => 'email_notification', 'params' => [ 'calendar_id' => $event->calendar_id, 'event_id' => $event->id ] ], 'label' => __('Email Notification', 'fluent-booking'), 'elIcon' => 'Message' ], 'sms_notification' => [ 'type' => 'route', 'visible' => true, 'disable' => true, 'route' => [ 'name' => 'sms_notification', 'params' => [ 'calendar_id' => $event->calendar_id, 'event_id' => $event->id ] ], 'label' => __('SMS Notification', 'fluent-booking'), 'elIcon' => 'Notification' ], 'recurring_settings' => [ 'type' => 'route', 'visible' => ($event->isOneToOne() || $event->isGroup()) ? true : false, 'disable' => true, 'route' => [ 'name' => 'recurring_settings', 'params' => [ 'calendar_id' => $event->calendar_id, 'event_id' => $event->id ] ], 'label' => __('Recurring Settings', 'fluent-booking'), 'svgIcon' => '' ], 'advanced_settings' => [ 'type' => 'route', 'visible' => true, 'disable' => true, 'route' => [ 'name' => 'advanced_settings', 'params' => [ 'calendar_id' => $event->calendar_id, 'event_id' => $event->id ] ], 'label' => __('Advanced Settings', 'fluent-booking'), 'elIcon' => 'Operation' ], 'payment_settings' => [ 'type' => 'route', 'visible' => true, 'disable' => true, 'route' => [ 'name' => 'payment_settings', 'params' => [ 'calendar_id' => $event->calendar_id, 'event_id' => $event->id ] ], 'label' => __('Payment Settings', 'fluent-booking'), 'elIcon' => 'Money' ], 'webhook_settings' => [ 'type' => 'route', 'visible' => true, 'disable' => true, 'route' => [ 'name' => 'webhook_settings', 'params' => [ 'calendar_id' => $event->calendar_id, 'event_id' => $event->id ] ], 'label' => __('Webhooks Feeds', 'fluent-booking'), 'elIcon' => 'Link' ], 'integrations' => [ 'type' => 'route', 'visible' => true, 'disable' => false, 'route' => [ 'name' => 'integrations', 'params' => [ 'calendar_id' => $event->calendar_id, 'event_id' => $event->id ] ], 'label' => __('Integrations', 'fluent-booking'), 'elIcon' => 'Connection' ] ], $event); } public static function getCalendarSettingsMenuItems($calendar) { return apply_filters('fluent_booking/calendar_setting_menu_items', [ 'calendar_settings' => [ 'type' => 'route', 'visible' => true, 'disable' => false, 'route' => [ 'name' => 'calendar_settings', 'params' => [ 'calendar_id' => $calendar->id ] ], 'label' => __('Calendar Settings', 'fluent-booking'), 'svgIcon' => '' ], 'calendar_events' => [ 'type' => 'route', 'visible' => true, 'disable' => false, 'route' => [ 'name' => 'calendar_events', 'params' => [ 'calendar_id' => $calendar->id ] ], 'label' => __('Event Types', 'fluent-booking'), 'svgIcon' => '' ], 'remote_calendars' => [ 'type' => 'route', 'visible' => true, 'disable' => true, 'route' => [ 'name' => 'remote_calendars', 'params' => [ 'calendar_id' => $calendar->id ] ], 'label' => __('Remote Calendars', 'fluent-booking'), 'svgIcon' => '' ], 'zoom_meeting' => [ 'type' => 'route', 'visible' => true, 'disable' => true, 'route' => [ 'name' => 'user_zoom_integration', 'params' => [ 'calendar_id' => $calendar->id ] ], 'label' => __('Zoom Integration', 'fluent-booking'), 'svgIcon' => '' ] ], $calendar); } }