PluginProbe
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution / 2.5.0
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution v2.5.0
2.5.0 2.4.0 2.3.0 2.2.5 2.2.0 2.1.2 2.1.1 trunk 1.10.0 1.10.01 1.10.02 1.5.0 1.5.01 1.5.02 1.5.1 1.5.10 1.5.20 1.5.21 1.5.22 1.5.23 1.5.24 1.5.25 1.6.0 1.7.0 1.7.1 All 34 releases
← All changes | app/Hooks/Handlers/AdminMenuHandler.php +247 -101 1.7.1 → 2.5.0 View file →
@@ -2,14 +2,16 @@
2 2
3 3 namespace FluentBooking\App\Hooks\Handlers;
4 4
5 5 use FluentBooking\App\App;
6 +use FluentBooking\App\Vite;
7 +use FluentBooking\App\Models\Booking;
6 8 use FluentBooking\App\Models\Calendar;
7 -use FluentBooking\App\Models\CalendarSlot;
8 9 use FluentBooking\App\Services\DateTimeHelper;
9 10 use FluentBooking\App\Services\Helper;
10 11 use FluentBooking\App\Services\PermissionManager;
11 12 use FluentBooking\App\Services\TransStrings;
13 +use FluentBooking\Framework\Support\Arr;
12 14
13 15 class AdminMenuHandler
14 16 {
15 17 public function register()
@@ -15,16 +17,50 @@
15 17 public function register()
16 18 {
17 19 add_action('admin_menu', [$this, 'add']);
18 20
19 - add_action('admin_enqueue_scripts', function () {
20 - if (!isset($_REQUEST['page']) || $_REQUEST['page'] != 'fluent-booking') { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
21 + add_action('admin_enqueue_scripts', function ($hookSuffix) {
22 + if ($hookSuffix !== 'toplevel_page_fluent-booking') {
21 23 return;
22 24 }
25 +
23 26 $this->enqueueAssets();
27 +
28 + add_action('admin_head', function () {
29 + $this->printThemeClass();
30 + }, 1);
31 +
24 32 }, 100);
25 33 }
26 34
35 + /**
36 + * Put the dark class on the document before the page paints.
37 + *
38 + * The theme is chosen in the browser, so the server cannot know it - which means
39 + * without this the first frame is always light, and somebody on dark gets a white flash
40 + * on every page load. This runs in <head>, before wp-admin's own markup, and is the
41 + * only reason it is inline rather than in global_admin.js: a file in the footer is too
42 + * late to matter, and one in the header is still a request the paint would wait on.
43 + *
44 + * `fluent_theme_mode` is the key every Fluent plugin shares (see global_admin.js), and it
45 + * holds a bare `light`, `dark` or `system`, so `system` is resolved here against the
46 + * machine's own preference. <body> does not exist yet, so only <html> is marked;
47 + * global_admin.js puts the same classes on <body> and #wpbody-content once it runs.
48 + *
49 + * @return void
50 + */
51 + public function printThemeClass()
52 + {
53 + $script = <<<'JS'
54 +(function(){try{var s=localStorage.getItem('fluent_theme_mode')||'';
55 +if(!s){s=localStorage.getItem('fcal_color_mode')==='dark'?'dark':'light';}
56 +if(s==='system'){s='system:'+(window.matchMedia&&window.matchMedia('(prefers-color-scheme: dark)').matches?'dark':'light');}
57 +if(s==='dark'||s==='system:dark'){var e=document.documentElement;e.classList.add('fluent_theme_dark');e.classList.add('dark');}}catch(e){}})();
58 +JS;
59 +
60 + echo '<script id="fluent-booking-theme-class">' . $script . '</script>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
61 + }
62 +
27 63 public function add()
28 64 {
29 65 $capability = PermissionManager::getMenuPermission();
30 66 $menuPriority = 26;
@@ -82,14 +118,94 @@
82 118 add_submenu_page(
83 119 'fluent-booking',
84 120 __('Settings', 'fluent-booking'),
85 121 __('Settings', 'fluent-booking'),
86 - 'manage_options',
122 + $capability,
87 123 'fluent-booking#/settings/general-settings',
88 124 [$this, 'render']
89 125 );
126 +
127 + if ($this->shouldShowUpgrade()) {
128 + add_submenu_page(
129 + 'fluent-booking',
130 + __('Upgrade to Pro', 'fluent-booking'),
131 + '<span class="fcal_upgrade_pro_menu">' . esc_html__('Upgrade to Pro', 'fluent-booking') . '</span>',
132 + 'manage_options',
133 + Helper::getUpgradeUrl('admin_sidebar_menu')
134 + );
135 +
136 + add_action('admin_head', [$this, 'printUpgradeMenuStyle']);
137 + add_action('admin_footer', [$this, 'printUpgradeMenuScript']);
138 + }
90 139 }
91 140
141 + /**
142 + * The upsell is for whoever can buy the license, so it stays hidden once Pro is loaded
143 + * and from hosts who only reach the app through their own calendars.
144 + *
145 + * @return bool
146 + */
147 + public function shouldShowUpgrade()
148 + {
149 + return !defined('FLUENT_BOOKING_PRO_DIR_FILE') && current_user_can('manage_options');
150 + }
151 +
152 + /**
153 + * Onboarding asks on its final step. The dashboard card waits until the site has a completed
154 + * booking, when FluentBooking has proven useful, and counts one the way the Bookings page's
155 + * "Completed" tab does.
156 + *
157 + * @return array
158 + */
159 + private function getOptinVars()
160 + {
161 + $showPrompt = $this->showOptinPrompt();
162 +
163 + return [
164 + 'share_stats' => Arr::get((array) Helper::getMeta('option', 0, 'optin'), 'share_stats', ''),
165 + 'can_manage' => PermissionManager::userCan('manage_all_data'),
166 + 'show_prompt' => $showPrompt,
167 + 'show_card' => $showPrompt && Booking::applyComputedStatus('completed')->exists(),
168 + ];
169 + }
170 +
171 + /**
172 + * The email opt-in prompt (dashboard card and onboarding) is for free sites that have not
173 + * subscribed yet. Closing it hides it for 30 days.
174 + *
175 + * @return bool
176 + */
177 + private function showOptinPrompt()
178 + {
179 + if (defined('FLUENT_BOOKING_PRO_DIR_FILE') || !PermissionManager::userCan('manage_all_data')) {
180 + return false;
181 + }
182 +
183 + $optin = (array) Helper::getMeta('option', 0, 'optin');
184 +
185 + if (!empty($optin['email'])) {
186 + return false;
187 + }
188 +
189 + return empty($optin['dismissed_at']) || (time() - (int) $optin['dismissed_at']) > 30 * DAY_IN_SECONDS;
190 + }
191 +
192 + public function printUpgradeMenuStyle()
193 + {
194 + echo '<style id="fluent-booking-upgrade-menu">#adminmenu .fcal_upgrade_pro_menu{color:#72aee6;font-weight:500;}</style>';
195 + }
196 +
197 + /**
198 + * The submenu points off-site, and WordPress gives submenu links no target, so it would
199 + * otherwise take the admin away from the dashboard.
200 + *
201 + * @return void
202 + */
203 + public function printUpgradeMenuScript()
204 + {
205 + echo '<script id="fluent-booking-upgrade-menu-js">(function(){var s=document.querySelector("#adminmenu .fcal_upgrade_pro_menu");if(s&&s.parentNode){s.parentNode.setAttribute("target","_blank");s.parentNode.setAttribute("rel","noopener");}})();</script>';
206 + }
207 +
92 208 public function render()
93 209 {
94 210 if (!as_has_scheduled_action('fluent_booking_five_minutes_tasks')) {
95 211 as_schedule_recurring_action(time(), (60 * 5), 'fluent_booking_five_minutes_tasks', [], 'fluent-booking', true);
@@ -94,9 +210,17 @@
94 210 if (!as_has_scheduled_action('fluent_booking_five_minutes_tasks')) {
95 211 as_schedule_recurring_action(time(), (60 * 5), 'fluent_booking_five_minutes_tasks', [], 'fluent-booking', true);
96 212 }
97 213
214 + // The daily task carries the summary report and the MCP record purge.
215 + // A site that never got it — a secondary blog on a network activation,
216 + // or one that lost it — would grow fcal_mcp_g_* rows forever.
217 + if (!as_has_scheduled_action('fluent_booking/daily_tasks')) {
218 + as_schedule_recurring_action(time(), (60 * 60 * 24), 'fluent_booking/daily_tasks', [], 'fluent-booking', true);
219 + }
220 +
98 221 $this->changeFooter();
222 +
99 223 $app = App::getInstance();
100 224
101 225 $config = $app->config;
102 226
@@ -125,9 +249,9 @@
125 249 ],
126 250 [
127 251 'key' => 'scheduled-events',
128 252 'label' => __('Bookings', 'fluent-booking'),
129 - 'permalink' => $baseUrl . 'scheduled-events?period=upcoming&author=me',
253 + 'permalink' => $baseUrl . 'scheduled-events',
130 254 ],
131 255 [
132 256 'key' => 'availability',
133 257 'label' => __('Availability', 'fluent-booking'),
@@ -134,10 +258,11 @@
134 258 'permalink' => $baseUrl . 'availability'
135 259 ]
136 260 ];
137 261
138 - if (current_user_can('manage_options')) {
139 - $menuItems[] = [
262 + $settingItems = [];
263 + if (PermissionManager::userCan('manage_all_data')) {
264 + $settingItems = [
140 265 'key' => 'settings',
141 266 'label' => __('Settings', 'fluent-booking'),
142 267 'permalink' => $baseUrl . 'settings/general-settings'
143 268 ];
@@ -142,28 +267,25 @@
142 267 'permalink' => $baseUrl . 'settings/general-settings'
143 268 ];
144 269 }
145 270
146 - if (!defined('FLUENT_BOOKING_PRO_DIR_FILE')) {
147 - $menuItems[] = [
148 - 'key' => 'buy',
149 - 'label' => __('Upgrade to Pro', 'fluent-booking'),
150 - 'permalink' => Helper::getUpgradeUrl()
151 - ];
152 - }
153 -
154 271 $menuItems = apply_filters('fluent_booking/admin_menu_items', $menuItems);
155 272
156 273 $assets = $app['url.assets'];
157 274
158 275 $portalVars = apply_filters('fluent_booking/admin_portal_vars', [
159 - 'name' => $name,
160 - 'slug' => $slug,
161 - 'menuItems' => $menuItems,
162 - 'baseUrl' => $baseUrl,
163 - 'logo' => $assets . 'images/logo.svg',
276 + 'name' => $name,
277 + 'slug' => $slug,
278 + 'menuItems' => $menuItems,
279 + 'settings' => $settingItems,
280 + 'baseUrl' => $baseUrl,
281 + 'logo' => $assets . 'images/logo.svg',
282 + 'dark_logo' => $assets . 'images/logo_dark.svg',
283 + 'upgradeUrl' => $this->shouldShowUpgrade() ? Helper::getUpgradeUrl('top_bar') : '',
164 284 ]);
165 285
286 + do_action('fluent_booking/admin_app_rendering');
287 +
166 288 $app->view->render('admin.menu', $portalVars);
167 289 }
168 290
169 291 public function changeFooter()
@@ -178,22 +300,20 @@
178 300 return FLUENT_BOOKING_VERSION;
179 301 });
180 302 }
181 303
182 - public function enqueueAssets()
304 + public function enqueueAssets($withApp = true)
183 305 {
184 306 $app = App::getInstance();
185 307
186 308 $isRtl = Helper::fluentbooking_is_rtl();
187 309
188 - $assets = $app['url.assets'];
310 + $slug = $app->config->get('app.slug');
189 311
190 - $slug = $app->config->get('app.slug');
312 + $assetsVersion = Vite::isDev() ? time() : FLUENT_BOOKING_ASSETS_VERSION;
191 313
192 - $adminAppCss = 'admin/admin.css';
193 314 if ($isRtl) {
194 - $adminAppCss = 'admin/admin-rtl.css';
195 - wp_enqueue_style('fluentbooking_admin_rtl', $assets . 'admin/fluentbooking_admin_rtl.css', [], FLUENT_BOOKING_ASSETS_VERSION);
315 + Vite::enqueueStyle('fluentbooking_admin_rtl', 'admin_rtl_css', [], $assetsVersion);
196 316 }
197 317
198 318 add_action('wp_print_scripts', function () {
199 319
@@ -284,28 +404,22 @@
284 404 wp_dequeue_style($wp_styles->registered[$script]->handle);
285 405 }
286 406 }, 999999);
287 407
288 - wp_enqueue_style('fluent_booing_admin_app', $assets . $adminAppCss, [], FLUENT_BOOKING_ASSETS_VERSION, 'all');
408 + Vite::enqueueStyle('fluent_booing_admin_app', 'admin_css', [], $assetsVersion, 'all');
289 409
290 - do_action($slug . '_loading_app');
410 + // The dashboard payload is per-user and includes every host's email address,
411 + // so the app bundle is only ever emitted for an authenticated, permitted viewer.
412 + if (!$withApp || !get_current_user_id()) {
413 + return;
414 + }
291 415
292 - wp_enqueue_script(
293 - $slug . '_admin_app',
294 - $assets . 'admin/app.js',
295 - array('jquery'),
296 - FLUENT_BOOKING_ASSETS_VERSION,
297 - true
298 - );
416 + do_action($slug . '_loading_app'); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound
299 417
300 - wp_enqueue_script(
301 - $slug . '_global_admin',
302 - $assets . 'admin/global_admin.js',
303 - array(),
304 - FLUENT_BOOKING_ASSETS_VERSION,
305 - true
306 - );
418 + Vite::enqueueScript($slug . '_admin_app', 'admin_app', array('jquery'), $assetsVersion);
307 419
420 + Vite::enqueueScript($slug . '_global_admin', 'global_admin', array(), $assetsVersion);
421 +
308 422 if (function_exists('wp_enqueue_editor')) {
309 423 add_filter('user_can_richedit', '__return_true');
310 424 wp_enqueue_editor();
311 425 wp_enqueue_media();
@@ -360,9 +474,8 @@
360 474 $weekSelectTimes = Helper::getWeekSelectTimes();
361 475 $overrideSelectTimes = Helper::getOverrideSelectTimes();
362 476 $statusChangingTimes = Helper::getBookingStatusChangingTimes();
363 477 $defaultTermsAndConditions = Helper::getDefaultTermsAndConditions();
364 - $locationFields = (new CalendarSlot())->getLocationFields();
365 478
366 479 return apply_filters('fluent_booking/admin_vars', [
367 480 'slug' => $slug = $app->config->get('app.slug'),
368 481 'nonce' => wp_create_nonce($slug),
@@ -374,9 +487,8 @@
374 487 'multi_durations' => $multiDurations,
375 488 'buffer_times' => $bufferTimes,
376 489 'slot_intervals' => $slotIntervals,
377 490 'schedule_schema' => $scheduleSchema,
378 - 'location_fields' => $locationFields,
379 491 'custom_field_types' => $customFieldTypes,
380 492 'week_select_times' => $weekSelectTimes,
381 493 'duration_lookup' => $durationLookup,
382 494 'multi_duration_lookup' => $multiDurationLookup,
@@ -386,8 +498,9 @@
386 498 'me' => [
387 499 'id' => $currentUser->ID,
388 500 'calendar_id' => $calendarId,
389 501 'full_name' => $currentUsername,
502 + 'avatar' => get_avatar_url($currentUser->ID, ['size' => 96]),
390 503 'email' => $currentUser->user_email,
391 504 'is_admin' => $hasAllAccess,
392 505 'permissions' => PermissionManager::getUserPermissions($currentUser, false),
393 506 ],
@@ -394,10 +507,12 @@
394 507 'all_hosts' => Calendar::getAllHosts(),
395 508 'is_new' => $isNew,
396 509 'require_slug' => $requireSlug,
397 510 'site_url' => site_url('/'),
511 + 'site_title' => get_bloginfo('name'),
398 512 'upgrade_url' => Helper::getUpgradeUrl(),
399 513 'timezones' => DateTimeHelper::getTimeZones(true),
514 + 'features' => Helper::getFeatures(),
400 515 'supported_features' => apply_filters('fluent_booking/supported_featured', [
401 516 'multi_users' => true
402 517 ]),
403 518 'i18' => [
@@ -402,18 +517,25 @@
402 517 ]),
403 518 'i18' => [
404 519 'date_time_config' => DateTimeHelper::getI18nDateTimeConfig(),
405 520 ],
406 - 'has_pro' => defined('FLUENT_BOOKING_PRO_DIR_FILE'),
407 - 'require_upgrade' => defined('FLUENT_BOOKING_PRO_DIR_FILE') && !defined('FLUENT_BOOKING_LITE'),
408 - 'dashboard_notices' => apply_filters('fluent_booking/dashboard_notices', []),
409 - 'trans' => TransStrings::getStrings(),
410 - 'date_format' => DateTimeHelper::getDateFormatter(true),
411 - 'time_format' => DateTimeHelper::getTimeFormatter(true),
412 - 'date_time_formatter' => DateTimeHelper::getDateFormatter(true) . ', ' . DateTimeHelper::getTimeFormatter(true),
413 - 'available_date_formats' => DateTimeHelper::getAvailableDateFormats(),
414 - 'admin_url' => admin_url(),
415 - 'is_rtl' => Helper::fluentbooking_is_rtl()
521 + 'has_pro' => defined('FLUENT_BOOKING_PRO_DIR_FILE'),
522 + 'require_upgrade' => defined('FLUENT_BOOKING_PRO_DIR_FILE') && !defined('FLUENT_BOOKING_LITE'),
523 + 'dashboard_notices' => apply_filters('fluent_booking/dashboard_notices', []),
524 + 'optin' => $this->getOptinVars(),
525 + 'payment_methods' => apply_filters('fluent_booking/payment/get_all_methods', []),
526 + 'trans' => TransStrings::getStrings(),
527 + 'date_format' => DateTimeHelper::getDateFormatter(true),
528 + 'time_format' => DateTimeHelper::getTimeFormatter(true),
529 + 'date_time_formatter' => DateTimeHelper::getDateFormatter(true) . ', ' . DateTimeHelper::getTimeFormatter(true),
530 + 'available_date_formats' => DateTimeHelper::getAvailableDateFormats(),
531 + 'default_booking_filters' => Helper::getDefaultBookingFilters(),
532 + 'default_paginations' => Helper::getDefaultPaginations(),
533 + 'pref_settings' => Helper::getPrefSettings(),
534 + 'settings_menu_items' => static::settingsMenuItems(),
535 + 'admin_url' => admin_url(),
536 + 'is_rtl' => Helper::fluentbooking_is_rtl(),
537 + 'iframe_html' => Helper::getIframeHtml()
416 538 ]);
417 539 }
418 540
419 541 protected function getRestInfo($app)
@@ -482,9 +604,9 @@
482 604 $urlAssets = $app['url.assets'];
483 605
484 606 return apply_filters('fluent_booking/settings_menu_items', [
485 607 'general_settings' => [
486 - 'title' => __('General Settings', 'fluent-booking'),
608 + 'title' => __('General', 'fluent-booking'),
487 609 'disable' => false,
488 610 'el_icon' => 'Operation',
489 611 'component_type' => 'StandAloneComponent',
490 612 'class' => 'general_settings',
@@ -501,8 +623,44 @@
501 623 'route' => [
502 624 'name' => 'team_members'
503 625 ]
504 626 ],
627 + 'payment_settings' => [
628 + 'title' => __('Payment', 'fluent-booking'),
629 + 'disable' => true,
630 + 'el_icon' => 'Money',
631 + 'component_type' => 'PaymentSettingsComponent',
632 + 'class' => 'payment_settings',
633 + 'route' => [
634 + 'name' => 'global_payment_settings',
635 + ],
636 + 'children' => [
637 + 'payment_settings' => [
638 + 'title' => __('Settings', 'fluent-booking'),
639 + 'disable' => true,
640 + 'route' => [
641 + 'name' => 'global_payment_settings'
642 + ]
643 + ],
644 + 'payment_methods' => [
645 + 'title' => __('Payment Methods', 'fluent-booking'),
646 + 'disable' => true,
647 + 'route' => [
648 + 'name' => 'payment_methods',
649 + 'params' => [
650 + 'settings_key' => 'stripe'
651 + ]
652 + ]
653 + ],
654 + 'payment_coupons' => [
655 + 'title' => __('Coupons', 'fluent-booking'),
656 + 'disable' => true,
657 + 'route' => [
658 + 'name' => 'payment_coupons'
659 + ]
660 + ]
661 + ],
662 + ],
505 663 'google' => [
506 664 'title' => __('Google Calendar / Meet', 'fluent-booking'),
507 665 'disable' => true,
508 666 'icon_url' => $urlAssets . 'images/gg-calendar.svg',
@@ -540,9 +698,9 @@
540 698 ],
541 699 'next_cloud_calendar' => [
542 700 'title' => __('Nextcloud Calendar', 'fluent-booking'),
543 701 'disable' => true,
544 - 'icon_url' => $urlAssets . 'images/Ncloud.svg',
702 + 'svg_icon' => '<svg xmlns="http://www.w3.org/2000/svg" height="800" width="1200" viewBox="-17.0838 -18.65685 148.0596 111.9411"><path d="M57.0328 0C45.2275 0 35.2216 8.0032 32.1204 18.8466c-2.6952-5.7515-8.5359-9.781-15.2633-9.781C7.6053 9.0657 0 16.671 0 25.9228c0 9.2519 7.6052 16.8606 16.857 16.8606 6.7275 0 12.5682-4.0319 15.2635-9.7844 3.1011 10.8442 13.107 18.85 24.9123 18.85 11.718 0 21.6729-7.885 24.8533-18.607 2.745 5.622 8.5135 9.5414 15.1454 9.5414 9.2518 0 16.8605-7.6087 16.8605-16.8606 0-9.2518-7.6087-16.857-16.8605-16.857-6.632 0-12.4003 3.917-15.1454 9.5378C78.7057 7.8825 68.7507 0 57.0328 0zm0 9.8955c8.9116 0 16.0307 7.1156 16.0307 16.0272s-7.119 16.0308-16.0307 16.0308c-8.9116 0-16.0272-7.1192-16.0272-16.0308S48.1212 9.8955 57.0328 9.8955zm-40.1757 9.0657c3.9044 0 6.965 3.0571 6.965 6.9615s-3.0606 6.965-6.965 6.965-6.9616-3.0606-6.9616-6.965 3.0572-6.9615 6.9616-6.9615zm80.1744 0c3.9044 0 6.965 3.0571 6.965 6.9615s-3.0606 6.965-6.965 6.965-6.9616-3.0606-6.9616-6.965 3.0572-6.9615 6.9616-6.9615z" color="currentColor" font-weight="400" font-family="sans-serif" overflow="visible" fill="currentColor"></path><path d="M29.1085 63.7615c2.7752 0 4.3275 1.9756 4.3275 4.939 0 .2822-.2352.5174-.5174.5174h-7.4792c.047 2.6342 1.8816 4.1394 3.9983 4.1394 1.3171 0 2.2579-.5644 2.7283-.9408.2822-.1881.5174-.141.6585.1412l.1411.2352c.1411.2351.094.4703-.1411.6585-.5645.4233-1.7875 1.129-3.4338 1.129-3.0575 0-5.4094-2.2109-5.4094-5.4095.047-3.3868 2.3048-5.4094 5.1272-5.4094zm2.8693 4.4216c-.094-2.1638-1.4112-3.2457-2.9164-3.2457-1.7404 0-3.2456 1.129-3.575 3.2457zm15.584-2.9164v-3.622c0-.3292.1882-.5174.5174-.5174h.3764c.3292 0 .4703.1882.4703.5174v2.446h2.1168c.3292 0 .5174.1882.5174.5174v.1412c0 .3292-.1882.4703-.5174.4703h-2.1168v5.1743c0 2.399 1.4582 2.6812 2.2579 2.7282.4233.047.5644.1411.5644.5174v.2823c0 .3292-.141.4704-.5644.4704-2.2579 0-3.622-1.3642-3.622-3.8102zm10.7718-1.5052c1.7875 0 2.9164.7526 3.4339 1.176.2351.188.2822.4233.047.7055l-.1411.2352c-.1882.2822-.4234.2822-.7056.094-.4704-.3292-1.3641-.9407-2.5871-.9407-2.2579 0-4.0453 1.6934-4.0453 4.1864 0 2.446 1.7874 4.1394 4.0453 4.1394 1.4582 0 2.446-.6585 2.9164-1.0819.2822-.1881.4704-.141.6585.1411l.1411.1882c.1411.2822.0941.4704-.141.7056-.5175.4233-1.7876 1.317-3.6691 1.317-3.0575 0-5.4094-2.2108-5.4094-5.4094.047-3.1986 2.399-5.4564 5.4564-5.4564zm6.2562-3.3398c0-.3292-.1882-.5174.141-.5174h.3764c.3293 0 .8467.1882.8467.5174V71.664c0 1.3171.6115 1.4582 1.0819 1.5053.2352 0 .4233.141.4233.4703v.3293c0 .3293-.1411.5174-.5174.5174-.8467 0-2.352-.2822-2.352-2.54zm9.6429 3.3398c3.0104 0 5.4564 2.3048 5.4564 5.3623 0 3.1046-2.446 5.4565-5.4564 5.4565-3.0105 0-5.4565-2.352-5.4565-5.4565 0-3.0575 2.446-5.3623 5.4565-5.3623zm0 9.5958c2.2108 0 3.9982-1.7875 3.9982-4.2335 0-2.3519-1.7874-4.0923-3.9982-4.0923s-4.0454 1.7875-4.0454 4.0923c.047 2.399 1.8346 4.2335 4.0454 4.2335zm23.4722-9.5958c2.493 0 3.3868 2.0697 3.3868 2.0697h.047s-.047-.3293-.047-.7997v-4.6568c0-.3293-.1412-.5174.1881-.5174h.3763c.3293 0 .8467.1881.8467.5174v13.406c0 .3292-.1411.5174-.4704.5174h-.3292c-.3293 0-.5175-.1411-.5175-.4704v-.7997c0-.3763.0941-.6585.0941-.6585h-.047s-.8938 2.1638-3.575 2.1638c-2.7752 0-4.5157-2.2108-4.5157-5.4095-.094-3.1986 1.8345-5.3623 4.5628-5.3623zm.047 9.5958c1.7404 0 3.3398-1.223 3.3398-4.1864 0-2.1167-1.082-4.1394-3.2927-4.1394-1.8345 0-3.3398 1.5052-3.3398 4.1394.047 2.54 1.3641 4.1864 3.2927 4.1864zm-85.8923.9878h.3763c.3293 0 .5174-.1881.5174-.5174V63.7274c0-1.5993 1.7404-2.7412 3.716-2.7412 1.9757 0 3.7161 1.142 3.7161 2.7412v10.1003c0 .3293.1882.5174.5174.5174h.3763c.3293 0 .4704-.1881.4704-.5174V63.6674c0-2.6812-2.6812-3.9983-5.1272-3.9983-2.3519 0-5.0331 1.317-5.0331 3.9983v10.1603c0 .3293.1411.5174.4704.5174zm78.5073-10.3485h-.3763c-.3293 0-.5175.1882-.5175.5175v5.6916c0 1.5993-1.0348 3.0575-3.0575 3.0575-1.9756 0-3.0575-1.4582-3.0575-3.0575v-5.6916c0-.3293-.1881-.5175-.5174-.5175h-.3763c-.3293 0-.4704.1882-.4704.5175v6.068c0 2.6811 1.9756 3.9982 4.4216 3.9982 2.446 0 4.4217-1.317 4.4217-3.9983v-6.068c.047-.3292-.1412-.5174-.4704-.5174zm-46.5643-.0771c-.1152.0184-.2258.0953-.3317.2214L41.5673 66.41l-1.4249 1.6978-2.1571-2.5715-1.1705-1.3955c-.1058-.1261-.2257-.195-.35-.2058-.1244-.0107-.2533.0366-.3795.1424l-.2884.2416c-.2523.2117-.2392.446-.0276.6982l1.9045 2.2693 1.5793 1.8815-2.3124 2.7553c-.0018.002-.0029.0043-.0046.0064l-1.1668 1.39c-.2117.2523-.188.5178.0643.7295l.2885.2407c.2522.2116.481.1585.6927-.0937l1.9036-2.2693 1.425-1.6977 2.157 2.5715c.0015.0016.0032.0029.0047.0046l1.1668 1.391c.2116.2521.4763.275.7285.0633l.2885-.2416c.2522-.2117.2392-.446.0275-.6983l-1.9045-2.2692-1.5792-1.8815 2.3124-2.7553c.0017-.002.0028-.0044.0046-.0064l1.1668-1.39c.2116-.2523.1879-.5179-.0643-.7295l-.2885-.2416c-.1261-.1058-.2459-.1452-.361-.1268z" fill="currentColor"></path></svg>',
545 703 'component_type' => 'GlobalSettingsComponent',
546 704 'class' => 'configure_nextcloud_calendar',
547 705 'route' => [
548 706 'name' => 'configure-integrations',
@@ -563,9 +721,9 @@
563 721 ],
564 722 'twilio' => [
565 723 'title' => __('SMS by Twilio', 'fluent-booking'),
566 724 'disable' => true,
567 - 'icon_url' => $urlAssets . 'images/tw.svg',
725 + 'svg_icon' => '<svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" viewBox="0 0 24 24" id="twilio"><path d="M0,12c0,6.64,5.359,12,12,12s12-5.36,12-12c0-6.641-5.36-12-12-12C5.381-0.008,0.008,5.352,0,11.971V12z M3.199,12c-0.014-4.846,3.904-8.786,8.75-8.801H12c4.847-0.014,8.786,3.904,8.801,8.75V12c0.015,4.847-3.904,8.786-8.75,8.801H12c-4.846,0.015-8.786-3.904-8.801-8.75V12z"></path><path d="M14.959 17.44c1.361 0 2.481-1.12 2.481-2.48 0-1.359-1.12-2.48-2.481-2.479-1.359 0-2.479 1.12-2.479 2.479C12.486 16.326 13.592 17.432 14.959 17.44zM14.959 11.52c1.361 0 2.481-1.12 2.481-2.479 0-1.361-1.12-2.481-2.481-2.481-1.359 0-2.479 1.12-2.479 2.481C12.487 10.407 13.593 11.513 14.959 11.52zM9.042 17.44c1.359 0 2.479-1.12 2.479-2.48 0-1.359-1.121-2.48-2.479-2.479-1.361 0-2.481 1.12-2.481 2.479C6.567 16.327 7.674 17.433 9.042 17.44zM9.042 11.52c1.359 0 2.479-1.12 2.479-2.479 0-1.361-1.121-2.481-2.479-2.481-1.361 0-2.481 1.12-2.481 2.481C6.567 10.408 7.675 11.513 9.042 11.52z"></path></svg>',
568 726 'component_type' => 'GlobalSettingsComponent',
569 727 'class' => 'configure_twilio',
570 728 'route' => [
571 729 'name' => 'configure-integrations',
@@ -573,47 +731,8 @@
573 731 'settings_key' => 'twilio'
574 732 ]
575 733 ]
576 734 ],
577 - 'stripe' => [
578 - 'title' => __('Stripe', 'fluent-booking'),
579 - 'disable' => true,
580 - 'icon_url' => $urlAssets . 'images/payment-methods/stripe.svg',
581 - 'component_type' => 'GlobalSettingsComponent',
582 - 'class' => 'stripe_payment',
583 - 'route' => [
584 - 'name' => 'PaymentSettingsIndex',
585 - 'params' => [
586 - 'settings_key' => 'stripe'
587 - ]
588 - ]
589 - ],
590 - 'paypal' => [
591 - 'title' => __('PayPal', 'fluent-booking'),
592 - 'disable' => true,
593 - 'icon_url' => $urlAssets . 'images/payment-methods/paypal.svg',
594 - 'component_type' => 'GlobalSettingsComponent',
595 - 'class' => 'paypal_payment',
596 - 'route' => [
597 - 'name' => 'PaymentSettingsIndex',
598 - 'params' => [
599 - 'settings_key' => 'paypal'
600 - ]
601 - ]
602 - ],
603 - 'offline' => [
604 - 'title' => __('Offline Payment', 'fluent-booking'),
605 - 'disable' => true,
606 - 'icon_url' => $urlAssets . 'images/payment-methods/offline.svg',
607 - 'component_type' => 'GlobalSettingsComponent',
608 - 'class' => 'offline_payment',
609 - 'route' => [
610 - 'name' => 'PaymentSettingsIndex',
611 - 'params' => [
612 - 'settings_key' => 'offline'
613 - ]
614 - ]
615 - ],
616 735 'license' => [
617 736 'title' => __('License', 'fluent-booking'),
618 737 'disable' => true,
619 738 'el_icon' => 'Lock',
@@ -621,9 +740,9 @@
621 740 'class' => 'configure_license',
622 741 'route' => [
623 742 'name' => 'license'
624 743 ]
625 - ],
744 + ]
626 745 ]);
627 746 }
628 747
629 748 public static function getEventSettingsMenuItems($event)
@@ -640,9 +759,9 @@
640 759 'event_id' => $event->id
641 760 ]
642 761 ],
643 762 'label' => __('Event Details', 'fluent-booking'),
644 - 'svgIcon' => '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"><path d="M8 2V5" stroke="#1B2533" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/><path d="M16 2V5" stroke="#1B2533" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/><path d="M7 13H15" stroke="#1B2533" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/><path d="M7 17H12" stroke="#1B2533" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/><path d="M16 3.5C19.33 3.68 21 4.95 21 9.65V15.83C21 19.95 20 22.01 15 22.01H9C4 22.01 3 19.95 3 15.83V9.65C3 4.95 4.67 3.69 8 3.5H16Z" stroke="#1B2533" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/></svg>'
763 + 'svgIcon' => '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"><path d="M8 2V5" stroke="currentColor" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/><path d="M16 2V5" stroke="currentColor" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/><path d="M7 13H15" stroke="currentColor" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/><path d="M7 17H12" stroke="currentColor" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/><path d="M16 3.5C19.33 3.68 21 4.95 21 9.65V15.83C21 19.95 20 22.01 15 22.01H9C4 22.01 3 19.95 3 15.83V9.65C3 4.95 4.67 3.69 8 3.5H16Z" stroke="currentColor" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/></svg>'
645 764 ],
646 765 'assignment' => [
647 766 'type' => 'route',
648 767 'visible' => false,
@@ -668,9 +787,9 @@
668 787 'event_id' => $event->id
669 788 ]
670 789 ],
671 790 'label' => __('Availability', 'fluent-booking'),
672 - 'svgIcon' => '<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="none"><path d="M6.66666 1.66699V4.16699" stroke="#445164" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/><path d="M13.3333 1.66699V4.16699" stroke="#445164" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/><path d="M2.91666 7.5752H17.0833" stroke="#445164" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/><path d="M17.5 7.08366V14.167C17.5 16.667 16.25 18.3337 13.3333 18.3337H6.66667C3.75 18.3337 2.5 16.667 2.5 14.167V7.08366C2.5 4.58366 3.75 2.91699 6.66667 2.91699H13.3333C16.25 2.91699 17.5 4.58366 17.5 7.08366Z" stroke="#445164" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/><path d="M13.0789 11.4167H13.0864" stroke="#445164" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M13.0789 13.9167H13.0864" stroke="#445164" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M9.99623 11.4167H10.0037" stroke="#445164" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M9.99623 13.9167H10.0037" stroke="#445164" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M6.91194 11.4167H6.91942" stroke="#445164" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M6.91194 13.9167H6.91942" stroke="#445164" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>'
791 + 'svgIcon' => '<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="none"><path d="M6.66666 1.66699V4.16699" stroke="currentColor" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/><path d="M13.3333 1.66699V4.16699" stroke="currentColor" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/><path d="M2.91666 7.5752H17.0833" stroke="currentColor" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/><path d="M17.5 7.08366V14.167C17.5 16.667 16.25 18.3337 13.3333 18.3337H6.66667C3.75 18.3337 2.5 16.667 2.5 14.167V7.08366C2.5 4.58366 3.75 2.91699 6.66667 2.91699H13.3333C16.25 2.91699 17.5 4.58366 17.5 7.08366Z" stroke="currentColor" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/><path d="M13.0789 11.4167H13.0864" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M13.0789 13.9167H13.0864" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M9.99623 11.4167H10.0037" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M9.99623 13.9167H10.0037" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M6.91194 11.4167H6.91942" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M6.91194 13.9167H6.91942" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>'
673 792 ],
674 793 'limit_settings' => [
675 794 'type' => 'route',
676 795 'visible' => true,
@@ -696,9 +815,9 @@
696 815 'event_id' => $event->id
697 816 ]
698 817 ],
699 818 'label' => __('Question Settings', 'fluent-booking'),
700 - 'svgIcon' => '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M8 2V5" stroke="#292D32" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/><path d="M16 2V5" stroke="#292D32" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/><path d="M12 13.8714V13.6441C12 12.908 12.5061 12.5182 13.0121 12.2043C13.5061 11.9012 14 11.5115 14 10.797C14 9.8011 13.1085 9 12 9C10.8915 9 10 9.8011 10 10.797" stroke="#292D32" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/><path d="M11.9945 16.4587H12.0053" stroke="#292D32" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/><path d="M16 3.5C19.33 3.67504 21 4.91005 21 9.48055V15.4903C21 19.4968 20 21.5 15 21.5H9C4 21.5 3 19.4968 3 15.4903V9.48055C3 4.91005 4.67 3.68476 8 3.5H16Z" stroke="#292D32" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/></svg>'
819 + 'svgIcon' => '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M8 2V5" stroke="currentColor" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/><path d="M16 2V5" stroke="currentColor" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/><path d="M12 13.8714V13.6441C12 12.908 12.5061 12.5182 13.0121 12.2043C13.5061 11.9012 14 11.5115 14 10.797C14 9.8011 13.1085 9 12 9C10.8915 9 10 9.8011 10 10.797" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/><path d="M11.9945 16.4587H12.0053" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/><path d="M16 3.5C19.33 3.67504 21 4.91005 21 9.48055V15.4903C21 19.4968 20 21.5 15 21.5H9C4 21.5 3 19.4968 3 15.4903V9.48055C3 4.91005 4.67 3.68476 8 3.5H16Z" stroke="currentColor" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/></svg>'
701 820 ],
702 821 'email_notification' => [
703 822 'type' => 'route',
704 823 'visible' => true,
@@ -726,8 +845,22 @@
726 845 ],
727 846 'label' => __('SMS Notification', 'fluent-booking'),
728 847 'elIcon' => 'Notification'
729 848 ],
849 + 'recurring_settings' => [
850 + 'type' => 'route',
851 + 'visible' => ($event->isOneToOne() || $event->isGroup()) ? true : false,
852 + 'disable' => true,
853 + 'route' => [
854 + 'name' => 'recurring_settings',
855 + 'params' => [
856 + 'calendar_id' => $event->calendar_id,
857 + 'event_id' => $event->id
858 + ]
859 + ],
860 + 'label' => __('Recurring Settings', 'fluent-booking'),
861 + 'svgIcon' => '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16" height="16" fill="currentColor"><path d="M6 4H21C21.5523 4 22 4.44772 22 5V12H20V6H6V9L1 5L6 1V4ZM18 20H3C2.44772 20 2 19.5523 2 19V12H4V18H18V15L23 19L18 23V20Z"></path></svg>'
862 + ],
730 863 'advanced_settings' => [
731 864 'type' => 'route',
732 865 'visible' => true,
733 866 'disable' => true,
@@ -799,9 +932,22 @@
799 932 'calendar_id' => $calendar->id
800 933 ]
801 934 ],
802 935 'label' => __('Calendar Settings', 'fluent-booking'),
803 - 'svgIcon' => '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M12 15C13.6569 15 15 13.6569 15 12C15 10.3431 13.6569 9 12 9C10.3431 9 9 10.3431 9 12C9 13.6569 10.3431 15 12 15Z" stroke="#292D32" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/><path d="M2 12.8799V11.1199C2 10.0799 2.85 9.21994 3.9 9.21994C5.71 9.21994 6.45 7.93994 5.54 6.36994C5.02 5.46994 5.33 4.29994 6.24 3.77994L7.97 2.78994C8.76 2.31994 9.78 2.59994 10.25 3.38994L10.36 3.57994C11.26 5.14994 12.74 5.14994 13.65 3.57994L13.76 3.38994C14.23 2.59994 15.25 2.31994 16.04 2.78994L17.77 3.77994C18.68 4.29994 18.99 5.46994 18.47 6.36994C17.56 7.93994 18.3 9.21994 20.11 9.21994C21.15 9.21994 22.01 10.0699 22.01 11.1199V12.8799C22.01 13.9199 21.16 14.7799 20.11 14.7799C18.3 14.7799 17.56 16.0599 18.47 17.6299C18.99 18.5399 18.68 19.6999 17.77 20.2199L16.04 21.2099C15.25 21.6799 14.23 21.3999 13.76 20.6099L13.65 20.4199C12.75 18.8499 11.27 18.8499 10.36 20.4199L10.25 20.6099C9.78 21.3999 8.76 21.6799 7.97 21.2099L6.24 20.2199C5.33 19.6999 5.02 18.5299 5.54 17.6299C6.45 16.0599 5.71 14.7799 3.9 14.7799C2.85 14.7799 2 13.9199 2 12.8799Z" stroke="#292D32" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/></svg>'
936 + 'svgIcon' => '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M12 15C13.6569 15 15 13.6569 15 12C15 10.3431 13.6569 9 12 9C10.3431 9 9 10.3431 9 12C9 13.6569 10.3431 15 12 15Z" stroke="currentColor" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/><path d="M2 12.8799V11.1199C2 10.0799 2.85 9.21994 3.9 9.21994C5.71 9.21994 6.45 7.93994 5.54 6.36994C5.02 5.46994 5.33 4.29994 6.24 3.77994L7.97 2.78994C8.76 2.31994 9.78 2.59994 10.25 3.38994L10.36 3.57994C11.26 5.14994 12.74 5.14994 13.65 3.57994L13.76 3.38994C14.23 2.59994 15.25 2.31994 16.04 2.78994L17.77 3.77994C18.68 4.29994 18.99 5.46994 18.47 6.36994C17.56 7.93994 18.3 9.21994 20.11 9.21994C21.15 9.21994 22.01 10.0699 22.01 11.1199V12.8799C22.01 13.9199 21.16 14.7799 20.11 14.7799C18.3 14.7799 17.56 16.0599 18.47 17.6299C18.99 18.5399 18.68 19.6999 17.77 20.2199L16.04 21.2099C15.25 21.6799 14.23 21.3999 13.76 20.6099L13.65 20.4199C12.75 18.8499 11.27 18.8499 10.36 20.4199L10.25 20.6099C9.78 21.3999 8.76 21.6799 7.97 21.2099L6.24 20.2199C5.33 19.6999 5.02 18.5299 5.54 17.6299C6.45 16.0599 5.71 14.7799 3.9 14.7799C2.85 14.7799 2 13.9199 2 12.8799Z" stroke="currentColor" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/></svg>'
937 + ],
938 + 'calendar_events' => [
939 + 'type' => 'route',
940 + 'visible' => true,
941 + 'disable' => false,
942 + 'route' => [
943 + 'name' => 'calendar_events',
944 + 'params' => [
945 + 'calendar_id' => $calendar->id
946 + ]
947 + ],
948 + 'label' => __('Event Types', 'fluent-booking'),
949 + 'svgIcon' => '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M8 6H21" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/><path d="M8 12H21" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/><path d="M8 18H21" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/><path d="M3 6H3.01" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M3 12H3.01" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M3 18H3.01" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>'
804 950 ],
805 951 'remote_calendars' => [
806 952 'type' => 'route',
807 953 'visible' => true,